diff options
author | Christian Pointner <equinox@spreadspace.org> | 2015-07-26 17:13:42 (GMT) |
---|---|---|
committer | Christian Pointner <equinox@spreadspace.org> | 2015-07-26 17:13:42 (GMT) |
commit | 1324242685b63a511be3b3625f78e1ebf2dafc76 (patch) | |
tree | b83d4947415757fba945b7a91a25230c9d044144 /src | |
parent | c6bbdfbdde366bfbb37858e7dc9e0fc351595023 (diff) |
moved sources to src/
Diffstat (limited to 'src')
37 files changed, 6272 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..7e7b9dc --- /dev/null +++ b/src/Makefile @@ -0,0 +1,122 @@ +## +## rhctl +## +## Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> +## +## This file is part of rhctl. +## +## rhctl is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## any later version. +## +## rhctl is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with rhctl. If not, see <http://www.gnu.org/licenses/>. +## + +ifneq ($(MAKECMDGOALS),distclean) +include include.mk +endif + +EXE_SWITCHCTL := switchctl +EXE_SERIALCLIENT := serialclient +EXE_STDIOCLIENT := stdioclient +EXE_HEARTBEATCLIENT := heartbeatclient +EXE_LUACLIENT := luaclient + +COMMONOBJ := log.o \ + sig_handler.o \ + string_list.o \ + key_value_storage.o \ + utils.o + +SWITCHCTLOBJ := command_queue.o \ + client_list.o \ + opt-switchctl.o \ + switchctl.o + +SERIALCLIENTOBJ := opt-serialclient.o \ + serialclient.o + +STDIOCLIENTOBJ := opt-stdioclient.o \ + stdioclient.o + +HEARTBEATCLIENTOBJ := opt-heartbeatclient.o \ + heartbeatclient.o + +LUACLIENTOBJ := opt-luaclient.o \ + l_log.o \ + l_sig_handler.o \ + l_cmd.o \ + luaclient.o + + +SRC := $(COMMONOBJ:%.o=%.c) $(SWITCHCTLOBJ:%.o=%.c) $(SERIALCLIENTOBJ:%.o=%.c) $(STDIOCLIENTOBJ:%.o=%.c) $(HEARTBEATCLIENTOBJ:%.o=%.c) $(LUACLIENTOBJ:%.o=%.c) options.c + +.PHONY: clean distclean + +all: $(EXE_SWITCHCTL) $(EXE_SERIALCLIENT) $(EXE_STDIOCLIENT) $(EXE_HEARTBEATCLIENT) $(EXE_LUACLIENT) + +%.d: %.c + @set -e; rm -f $@; \ + $(CC) -MM $(CFLAGS) $< > $@.$$$$; \ + sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ + rm -f $@.$$$$; echo '(re)building $@' + +ifneq ($(MAKECMDGOALS),distclean) +-include $(SRC:%.c=%.d) +endif + +$(EXE_SWITCHCTL): $(COMMONOBJ) $(SWITCHCTLOBJ) + $(CC) $(COMMONOBJ) $(SWITCHCTLOBJ) -o $@ $(LDFLAGS) + +$(EXE_SERIALCLIENT): $(COMMONOBJ) $(SERIALCLIENTOBJ) + $(CC) $(COMMONOBJ) $(SERIALCLIENTOBJ) -o $@ $(LDFLAGS) + +$(EXE_STDIOCLIENT): $(COMMONOBJ) $(STDIOCLIENTOBJ) + $(CC) $(COMMONOBJ) $(STDIOCLIENTOBJ) -o $@ $(LDFLAGS) + +$(EXE_HEARTBEATCLIENT): $(COMMONOBJ) $(HEARTBEATCLIENTOBJ) + $(CC) $(COMMONOBJ) $(HEARTBEATCLIENTOBJ) -o $@ $(LDFLAGS) + +$(EXE_LUACLIENT): $(COMMONOBJ) $(LUACLIENTOBJ) + $(CC) $(COMMONOBJ) $(LUACLIENTOBJ) -o $@ $(LDFLAGS) $(LUA_LDFLAGS) + +opt-switchctl.o: options.c + $(CC) $(CFLAGS) -DOPT_SWITCHCTL -o $@ -c $< + +opt-serialclient.o: options.c + $(CC) $(CFLAGS) -DOPT_SERIALCLIENT -o $@ -c $< + +opt-stdioclient.o: options.c + $(CC) $(CFLAGS) -DOPT_STDIOCLIENT -o $@ -c $< + +opt-heartbeatclient.o: options.c + $(CC) $(CFLAGS) -DOPT_HEARTBEATCLIENT -o $@ -c $< + +opt-luaclient.o: options.c + $(CC) $(CFLAGS) -DOPT_LUACLIENT -o $@ -c $< + +%.o: %.c + $(CC) $(CFLAGS) -c $< + + +distclean: clean + find . -name *.o -exec rm -f {} \; + find . -name "*.\~*" -exec rm -rf {} \; + rm -f include.mk + +clean: + rm -f *.o + rm -f *.d + rm -f *.d.* + rm -f $(EXE_SWITCHCTL) + rm -f $(EXE_SERIALCLIENT) + rm -f $(EXE_STDIOCLIENT) + rm -f $(EXE_HEARTBEATCLIENT) + rm -f $(EXE_LUACLIENT)
\ No newline at end of file diff --git a/src/client_list.c b/src/client_list.c new file mode 100644 index 0000000..141a3a4 --- /dev/null +++ b/src/client_list.c @@ -0,0 +1,137 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <unistd.h> + +#include "client_list.h" +#include "datatypes.h" + +char* client_type_tostring(client_type_t type) +{ + switch(type) + { + case MASTER: return "master"; + case STANDBY: return "standby"; + case HB_MASTER: return "hb_master"; + case HB_STANDBY: return "hb_standby"; + case DEFAULT: return "unspecified"; + } + return "<invalid>"; +} + +client_t* client_get_last(client_t* first) +{ + if(!first) + return NULL; + + while(first->next) { + first = first->next; + } + + return first; +} + +int client_add(client_t** first, int fd) +{ + if(!first) + return -1; + + client_t* new_client = malloc(sizeof(client_t)); + if(!new_client) + return -2; + + new_client->fd = fd; + new_client->type = DEFAULT; + new_client->request_listener = 0; + new_client->mode_listener = 0; + new_client->status_listener = 0; + new_client->gpi_listener = 0; + new_client->oc_listener = 0; + new_client->relay_listener = 0; + new_client->silence_listener = 0; + new_client->health_listener = 0; + new_client->next = NULL; + new_client->buffer.offset = 0; + + if(!(*first)) { + *first = new_client; + return 0; + } + + client_get_last(*first)->next = new_client; + + return 0; +} + +void client_remove(client_t** first, int fd) +{ + if(!first || !(*first)) + return; + + client_t* deletee = *first; + if((*first)->fd == fd) { + *first = (*first)->next; + close(deletee->fd); + free(deletee); + return; + } + + client_t* prev = deletee; + deletee = deletee->next; + while(deletee) { + if(deletee->fd == fd) { + prev->next = deletee->next; + close(deletee->fd); + free(deletee); + return; + } + prev = deletee; + deletee = deletee->next; + } +} + +client_t* client_find(client_t* first, int fd) +{ + if(!first) + return NULL; + + while(first) { + if(first->fd == fd) + return first; + + first = first->next; + } + return NULL; +} + +void client_clear(client_t** first) +{ + if(!first || !(*first)) + return; + + while(*first) { + client_t* deletee = *first; + *first = (*first)->next; + close(deletee->fd); + free(deletee); + } +} diff --git a/src/client_list.h b/src/client_list.h new file mode 100644 index 0000000..2f3d5f2 --- /dev/null +++ b/src/client_list.h @@ -0,0 +1,52 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RHCTL_client_list_h_INCLUDED +#define RHCTL_client_list_h_INCLUDED + +#include "datatypes.h" + +enum client_type_enum { DEFAULT, MASTER, STANDBY, HB_MASTER, HB_STANDBY }; +typedef enum client_type_enum client_type_t; +char* client_type_tostring(client_type_t); + +struct client_struct { + int fd; + client_type_t type; + int request_listener; + int mode_listener; + int status_listener; + int gpi_listener; + int oc_listener; + int relay_listener; + int silence_listener; + int health_listener; + struct client_struct* next; + read_buffer_t buffer; +}; +typedef struct client_struct client_t; + +int client_add(client_t** first, int fd); +void client_remove(client_t** first, int fd); +client_t* client_find(client_t* first, int fd); +void client_clear(client_t** first); + +#endif diff --git a/src/command_queue.c b/src/command_queue.c new file mode 100644 index 0000000..b72d05e --- /dev/null +++ b/src/command_queue.c @@ -0,0 +1,118 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <string.h> + +#include "command_queue.h" +#include "datatypes.h" + +cmd_t* cmd_get_last(cmd_t* first) +{ + if(!first) + return NULL; + + while(first->next) { + first = first->next; + } + + return first; +} + +int cmd_push(cmd_t** first, int fd, cmd_id_t cmd, const char* param) +{ + if(!first) + return -1; + + cmd_t* new_cmd = malloc(sizeof(cmd_t)); + if(!new_cmd) + return -2; + + new_cmd->fd = fd; + new_cmd->cmd = cmd; + if(param) { + new_cmd->param = strdup(param); + if(!new_cmd->param) { + free(new_cmd); + return -2; + } + } + else + new_cmd->param = NULL; + new_cmd->sent = 0; + new_cmd->tv_start.tv_sec = 0; + new_cmd->tv_start.tv_usec = 0; + new_cmd->next = NULL; + + if(!(*first)) { + *first = new_cmd; + return 0; + } + + cmd_get_last(*first)->next = new_cmd; + + return 0; +} + +void cmd_sent(cmd_t* cmd) +{ + if(!cmd) + return; + + cmd->sent = 1; + gettimeofday(&cmd->tv_start, NULL); +} + +int cmd_has_expired(cmd_t cmd) +{ + struct timeval now; + timerclear(&now); + gettimeofday(&now, NULL); + cmd.tv_start.tv_sec++; + + return timercmp(&cmd.tv_start, &now, <); +} + +void cmd_pop(cmd_t** first) +{ + if(!first || !(*first)) + return; + + cmd_t* deletee = *first; + *first = (*first)->next; + if(deletee->param) + free(deletee->param); + free(deletee); +} + +void cmd_clear(cmd_t** first) +{ + if(!first || !(*first)) + return; + + while(*first) { + cmd_t* deletee = *first; + *first = (*first)->next; + if(deletee->param) + free(deletee->param); + free(deletee); + } +} diff --git a/src/command_queue.h b/src/command_queue.h new file mode 100644 index 0000000..4cdcf9f --- /dev/null +++ b/src/command_queue.h @@ -0,0 +1,46 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RHCTL_command_queue_h_INCLUDED +#define RHCTL_command_queue_h_INCLUDED + +#include <sys/time.h> + +enum cmd_id_enum { SWITCH, CHANNEL, TYPE, MODE, HEARTBEAT, STATUS, HEALTH, LOG, LISTEN }; +typedef enum cmd_id_enum cmd_id_t; + +struct cmd_struct { + int fd; + cmd_id_t cmd; + char* param; + int sent; + struct timeval tv_start; + struct cmd_struct* next; +}; +typedef struct cmd_struct cmd_t; + +int cmd_push(cmd_t** first, int fd, cmd_id_t cmd, const char* param); +void cmd_sent(cmd_t* cmd); +int cmd_has_expired(cmd_t cmd); +void cmd_pop(cmd_t** first); +void cmd_clear(cmd_t** first); + +#endif diff --git a/src/configure b/src/configure new file mode 100755 index 0000000..83fe4d3 --- /dev/null +++ b/src/configure @@ -0,0 +1,162 @@ +#!/bin/sh +# +# rhctl +# +# Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> +# +# This file is part of rhctl. +# +# rhctl is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# rhctl is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with rhctl. If not, see <http://www.gnu.org/licenses/>. +# + +TARGET=`uname -s` + +USE_CLANG=0 + +LUA_LDFLAGS='-lm' +LUA_DIR='' + +print_usage() { + echo "configure --help print this" + echo " --target=<TARGET> build target i.e. Linux (default: autodetect)" + echo " --with-lua=<DIR> use this lua tree instead of system default" + echo " --use-clang use clang/llvm as compiler/linker" +} + +for arg +do + case $arg in + --target=*) + TARGET=${arg#--target=} + ;; + --use-clang) + USE_CLANG=1 + ;; + --with-lua=*) + LUA_DIR=${arg#--with-lua=} + ;; + --help) + print_usage + exit 0 + ;; + *) + echo "Unknown argument: $arg" + print_usage + exit 1 + ;; + esac +done + +if [ $USE_CLANG -eq 0 ]; then + CFLAGS='-g -Wall -O2' + LDFLAGS='-g -Wall -O2' + COMPILER='gcc' +else + CFLAGS='-g -O2' + LDFLAGS='-g -O2' + COMPILER='clang' +fi + +rm -f include.mk +case $TARGET in + Linux) + echo "Linux specific build options" + LUA_LDFLAGS=$LUA_LDFLAGS' -ldl' + ;; + OpenBSD|FreeBSD|NetBSD) + echo "BSD specific build options" + CFLAGS=$CFLAGS' -I/usr/local/include' + LDFLAGS=$LDFLAGS' -L/usr/local/lib' + ;; + *) + echo "Plattform not supported" + exit 1; + ;; +esac + +test_lua_version() +{ + LUA_VERSION=`cat $1 | grep "#define LUA_VERSION[ ]" | cut -f2- | tr -d '"' | sed -e 's/Lua \([0-9][0-9.]*\)/\1/'` + LUA_VERSION_NUM=`cat $1 | grep "#define LUA_VERSION_NUM" | awk '{ print $3 }'` + LUA_RELEASE=`cat $1 | grep "#define LUA_RELEASE[ ]" | cut -f2-` + + if [ $LUA_VERSION_NUM -ge 501 ]; then + return 1; + else + return 0; + fi +} + +if [ -z "$LUA_DIR" ]; then + for prefix in /usr /usr/local; do + if [ -e $prefix/include/lua.h ]; then + test_lua_version $prefix/include/lua.h + if [ $? -eq 1 ]; then + echo "using Lua $LUA_VERSION ($LUA_RELEASE) found at $prefix/include" + CFLAGS="$CFLAGS -I$prefix/include" + LUA_LDFLAGS="$LUA_LDFLAGS -L$prefix/lib -llua" + LUA_DIR=found + break + fi + else + for dir in `ls -d $prefix/include/lua* 2> /dev/null`; do + if [ -e $dir/lua.h ]; then + test_lua_version $dir/lua.h + if [ $? -eq 1 ]; then + echo "using Lua $LUA_VERSION ($LUA_RELEASE) found at $dir" + CFLAGS="$CFLAGS -I$dir" + if [ -x "$prefix/bin/lua$LUA_VERSION" ]; then + LUA_LDFLAGS="$LUA_LDFLAGS -L$prefix/lib -llua$LUA_VERSION" + LUA_DIR=found + elif [ -x "$prefix/bin/lua-$LUA_VERSION" ]; then + LUA_LDFLAGS="$LUA_LDFLAGS -L$prefix/lib -llua-$LUA_VERSION" + LUA_DIR=found + else + echo "ERROR: found lua.h at $dir/lua.h but no matching lua library" + return 1 + fi + break + fi + fi + done + if [ -n "$LUA_DIR" ]; then + break + fi + fi + done + + if [ -z "$LUA_DIR" ]; then + echo "ERROR: no suitable lua found .. please install lua 5.1 or higher or use --with-lua" + return 1 + fi + +else + CFLAGS="$CFLAGS -I$LUA_DIR/include" + LUA_LDFLAGS="$LUA_LDFLAGS $LUA_DIR/lib/liblua.a" +fi + + +cat >> include.mk <<EOF +# this file was created automatically +# do not edit this file directly +# use ./configure instead + +TARGET := $TARGET +CC := $COMPILER +CFLAGS := $CFLAGS +LDFLAGS := $LDFLAGS +LUA_LDFLAGS := $LUA_LDFLAGS +EOF + +exit 0 diff --git a/src/daemon.h b/src/daemon.h new file mode 100644 index 0000000..9e6f1b2 --- /dev/null +++ b/src/daemon.h @@ -0,0 +1,175 @@ +/* + * uAnytun + * + * uAnytun is a tiny implementation of SATP. Unlike Anytun which is a full + * featured implementation uAnytun has no support for multiple connections + * or synchronisation. It is a small single threaded implementation intended + * to act as a client on small platforms. + * The secure anycast tunneling protocol (satp) defines a protocol used + * for communication between any combination of unicast and anycast + * tunnel endpoints. It has less protocol overhead than IPSec in Tunnel + * mode and allows tunneling of every ETHER TYPE protocol (e.g. + * ethernet, ip, arp ...). satp directly includes cryptography and + * message authentication based on the methodes used by SRTP. It is + * intended to deliver a generic, scaleable and secure solution for + * tunneling and relaying of packets of any protocol. + * + * + * Copyright (C) 2007-2010 Christian Pointner <equinox@anytun.org> + * + * This file is part of uAnytun. + * + * uAnytun is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uAnytun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uAnytun. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef UANYTUN_daemon_h_INCLUDED +#define UANYTUN_daemon_h_INCLUDED + +#include <poll.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <unistd.h> + +struct priv_info_struct { + struct passwd* pw_; + struct group* gr_; +}; +typedef struct priv_info_struct priv_info_t; + +int priv_init(priv_info_t* priv, const char* username, const char* groupname) +{ + if(!priv) + return -1; + + priv->pw_ = NULL; + priv->gr_ = NULL; + + priv->pw_ = getpwnam(username); + if(!priv->pw_) { + log_printf(ERROR, "unknown user %s", username); + return -1; + } + + if(groupname) + priv->gr_ = getgrnam(groupname); + else + priv->gr_ = getgrgid(priv->pw_->pw_gid); + + if(!priv->gr_) { + log_printf(ERROR, "unknown group %s", groupname); + return -1; + } + + return 0; +} + +int priv_drop(priv_info_t* priv) +{ + if(!priv || !priv->pw_ || !priv->gr_) { + log_printf(ERROR, "privileges not initialized properly"); + return -1; + } + + if(setgid(priv->gr_->gr_gid)) { + log_printf(ERROR, "setgid('%s') failed: %s", priv->gr_->gr_name, strerror(errno)); + return -1; + } + + gid_t gr_list[1]; + gr_list[0] = priv->gr_->gr_gid; + if(setgroups (1, gr_list)) { + log_printf(ERROR, "setgroups(['%s']) failed: %s", priv->gr_->gr_name, strerror(errno)); + return -1; + } + + if(setuid(priv->pw_->pw_uid)) { + log_printf(ERROR, "setuid('%s') failed: %s", priv->pw_->pw_name, strerror(errno)); + return -1; + } + + log_printf(NOTICE, "dropped privileges to %s:%s", priv->pw_->pw_name, priv->gr_->gr_name); + return 0; +} + + +int do_chroot(const char* chrootdir) +{ + if(getuid() != 0) { + log_printf(ERROR, "this program has to be run as root in order to run in a chroot"); + return -1; + } + + if(chroot(chrootdir)) { + log_printf(ERROR, "can't chroot to %s: %s", chrootdir, strerror(errno)); + return -1; + } + log_printf(NOTICE, "we are in chroot jail (%s) now", chrootdir); + if(chdir("/")) { + log_printf(ERROR, "can't change to /: %s", strerror(errno)); + return -1; + } + + return 0; +} + +void daemonize() +{ + pid_t pid; + + pid = fork(); + if(pid < 0) { + log_printf(ERROR, "daemonizing failed at fork(): %s, exitting", strerror(errno)); + exit(-1); + } + if(pid) exit(0); + + umask(0); + + if(setsid() < 0) { + log_printf(ERROR, "daemonizing failed at setsid(): %s, exitting", strerror(errno)); + exit(-1); + } + + pid = fork(); + if(pid < 0) { + log_printf(ERROR, "daemonizing failed at fork(): %s, exitting", strerror(errno)); + exit(-1); + } + if(pid) exit(0); + + if ((chdir("/")) < 0) { + log_printf(ERROR, "daemonizing failed at chdir(): %s, exitting", strerror(errno)); + exit(-1); + } + + int fd; + for (fd=0;fd<=2;fd++) // close all file descriptors + close(fd); + fd = open("/dev/null",O_RDWR); // stdin + if(fd == -1) + log_printf(WARNING, "can't open stdin (chroot and no link to /dev/null?)"); + else { + if(dup(fd) == -1) // stdout + log_printf(WARNING, "can't open stdout"); + if(dup(fd) == -1) // stderr + log_printf(WARNING, "can't open stderr"); + } + umask(027); +} + +#endif + diff --git a/src/datatypes.h b/src/datatypes.h new file mode 100644 index 0000000..3a72adc --- /dev/null +++ b/src/datatypes.h @@ -0,0 +1,54 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RHCTL_datatypes_h_INCLUDED +#define RHCTL_datatypes_h_INCLUDED + +#define _GNU_SOURCE +#include <stdint.h> +#include <arpa/inet.h> + +typedef uint8_t bool; +#define FALSE 0 +#define TRUE 1 + +typedef uint8_t u_int8_t; +typedef uint16_t u_int16_t; +typedef uint32_t u_int32_t; +typedef uint64_t u_int64_t; +/* typedef int8_t int8_t; */ +/* typedef int16_t int16_t; */ +/* typedef int32_t int32_t; */ +/* typedef int64_t int64_t; */ + +struct buffer_struct { + u_int32_t length_; + u_int8_t* buf_; +}; +typedef struct buffer_struct buffer_t; + +struct read_buffer_struct { + u_int32_t offset; + u_int8_t buf[100]; +}; +typedef struct read_buffer_struct read_buffer_t; + +#endif diff --git a/src/health-watch.lua b/src/health-watch.lua new file mode 100644 index 0000000..082c19d --- /dev/null +++ b/src/health-watch.lua @@ -0,0 +1,107 @@ +-- +-- rhctl +-- +-- Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> +-- +-- This file is part of rhctl. +-- +-- rhctl is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- any later version. +-- +-- rhctl is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with rhctl. If not, see <http://www.gnu.org/licenses/>. +-- + +package.path = package.path .. ';/usr/share/rhctl/?.lua' + +socket = require("socket") +utils = require("utils") + +old_state = {} +current_state = {} + +function state_changed() + for k,v in pairs(old_state) do + if v ~= current_state[k] then + return true + end + end + for k,v in pairs(current_state) do + if old_state[k] == nil then + return true + end + end + return false +end + +function process_cmd(message) + local exps = { hs = "^Health: (%a+)$", mc = "^Master: (%a+)$", sc = "^Standby: (%a+)$", hbm = "^Hearbeat Master: (%a+, %a+)$", hbs = "^Hearbeat Standby: (%a+, %a+)$" } + for key, exp in pairs(exps) do + local state = string.match(message, exp) + if(state) then + if(key == 'hs') then + current_state = {} + end + current_state[key] = state + end + end + + if(current_state['hbs'] ~= nil) then + if(state_changed()) then + body = "RHCTL Health Output:\n\n" + body = body .. "Health: " .. current_state['hs'] .. "\n" + body = body .. "Master: " .. current_state['mc'] .. "\n" + body = body .. "Standby: " .. current_state['sc'] .. "\n" + body = body .. "Master Hearbeat: " .. current_state['hbm'] .. "\n" + body = body .. "Standby Hearbeat: " .. current_state['hbs'] .. "\n" + utils.send_mail("logs@helsinki.at", "[RHCTL] health: " .. current_state['hs'], body) + old_state = current_state + end + end + + return 0 +end + +function main_loop(opt) + log.printf(log.NOTICE, "main_loop started") + local sig = signal.init() + local cmdfd = cmd.init() + + cmd.send_string("listen health"); + cmd.send_string("health"); + + local return_value = 0 + while return_value == 0 do + local readable, _, err = socket.select({ sig , cmdfd }, nil) + if(err) then + log.printf(log.ERROR, "select returned with error: %s", err) + return_value = -1 + else + for _, input in ipairs(readable) do + if(input == sig) then + return_value = signal.handle() + if(return_value == 1) then break end + else + if(input == cmdfd) then + return_value = cmd.recv_data(process_cmd) + if(return_value ~= 0) then break end + else + log.printf(log.ERROR, "select returned invalid handle??") + return_value = -1 + break; + end + end + end + end + end + + signal.stop() + return return_value +end diff --git a/src/heartbeatclient.c b/src/heartbeatclient.c new file mode 100644 index 0000000..3fe8423 --- /dev/null +++ b/src/heartbeatclient.c @@ -0,0 +1,426 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "log.h" +#include "sig_handler.h" +#include "options.h" + +#include "daemon.h" +#include "utils.h" + +int process_serial(read_buffer_t* buffer, int serial_fd, int led_pipe_fd, bool *state) +{ + int ret = 0; + struct timeval tv; + fd_set fds; + FD_ZERO(&fds); + FD_SET(serial_fd, &fds); + + for(;;) { + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(serial_fd+1, &fds, NULL, NULL, &tv); + if(!ret) + return 0; + else if(ret < 0) + return ret; + + ret = read(serial_fd, &buffer->buf[buffer->offset], 1); + if(!ret) + return 2; + if(ret == -1 && errno == EAGAIN) + return 0; + else if(ret < 0) + break; + + if(buffer->buf[buffer->offset] == '\n') { + buffer->buf[buffer->offset] = 0; + + if(buffer->offset > 0 && buffer->buf[buffer->offset-1] == '\r') + buffer->buf[buffer->offset-1] = 0; + + if(strlen((char *)(buffer->buf))) { + *state = TRUE; + if(led_pipe_fd >= 0) { + char buf = '1'; + ret = write(led_pipe_fd, &buf, 1); + if(ret == -1 && errno != EAGAIN) + log_printf(WARNING, "write to led process pipe returned with error: %s", strerror(errno)); + } + } + + buffer->offset = 0; + return 0; + } + + buffer->offset++; + if(buffer->offset >= sizeof(buffer->buf)) { + log_printf(DEBUG, "string too long (fd=%d)", serial_fd); + buffer->offset = 0; + return 0; + } + } + + return ret; +} + +int main_loop(int serial_fd, int cmd_fd, int led_pipe_fd, options_t* opt) +{ + log_printf(NOTICE, "entering main loop"); + + fd_set readfds, tmpfds; + FD_ZERO(&readfds); + FD_SET(serial_fd, &readfds); + FD_SET(cmd_fd, &readfds); + int max_fd = serial_fd > cmd_fd ? serial_fd : cmd_fd; + + int sig_fd = signal_init(); + if(sig_fd < 0) + return -1; + FD_SET(sig_fd, &readfds); + max_fd = (max_fd < sig_fd) ? sig_fd : max_fd; + + int return_value = 0; + return_value = send_string(cmd_fd, "type heartbeat\n"); + if(return_value <= 0) { + log_printf(ERROR, "error setting client type"); + return_value = -1; + } + else { + log_printf(NOTICE, "connecting as type heartbeat"); + return_value = 0; + } + + bool state = FALSE; + send_string(cmd_fd, "heartbeat 0\n"); + + read_buffer_t serial_buffer; + serial_buffer.offset = 0; + struct timeval timeout; + u_int32_t time = 0; + while(!return_value) { + memcpy(&tmpfds, &readfds, sizeof(tmpfds)); + + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + int ret = select(max_fd+1, &tmpfds, NULL, NULL, &timeout); + if(ret == -1 && errno != EINTR) { + log_printf(ERROR, "select returned with error: %s", strerror(errno)); + return_value = -1; + break; + } + if(ret == -1) + continue; + if(!ret) { + time++; + if(time >= opt->timeout_) { + if(state == TRUE) { + log_printf(WARNING, "heartbeat timeout"); + send_string(cmd_fd, "heartbeat 0\n"); + state = FALSE; + } + time = 0; + } + continue; + } + + if(FD_ISSET(sig_fd, &tmpfds)) + if(signal_handle()) + return_value = 1; + + if(FD_ISSET(serial_fd, &tmpfds)) { + bool old_state = state; + return_value = process_serial(&serial_buffer, serial_fd, led_pipe_fd, &state); + if(return_value) + break; + if(state) { + if(!old_state) { + log_printf(WARNING, "heartbeat returned"); + send_string(cmd_fd, "heartbeat 1\n"); + } + time = 0; + } + } + + if(FD_ISSET(cmd_fd, &tmpfds)) { + u_int8_t buf[100]; + int ret = recv(cmd_fd, buf, 100, 0); + if(!ret) + return_value = 3; + if(ret == -1 && errno == EAGAIN) + return_value = 0; + else if(ret < 0) + return_value = ret; + } + } + + signal_stop(); + return return_value; +} + +int led_process(int pipe_fd, int led_fd) +{ + struct timeval timeout; + char buf; + int ret; + + log_printf(INFO, "led_process: starting up"); + for(;;) { + ret = read(pipe_fd, &buf, 1); + if(ret == -1 && errno == EAGAIN) + continue; + else if(!ret || ret < 0) { + log_printf(ERROR, "led_process: read returned with error: %s", strerror(errno)); + break; + } + + buf = '1'; + ret = write(led_fd, &buf, 1); + if(ret == -1 && errno != EAGAIN) { + log_printf(ERROR, "led_process: write returned with error: %s", strerror(errno)); + break; + } + + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + ret = select(0, NULL, NULL, NULL, &timeout); + if(ret == -1 && errno != EINTR) { + log_printf(ERROR, "led_process: select returned with error: %s", strerror(errno)); + break; + } + + buf = '0'; + ret = write(led_fd, &buf, 1); + if(ret == -1 && errno != EAGAIN) { + log_printf(ERROR, "led_process: write returned with error: %s", strerror(errno)); + break; + } + } + log_printf(INFO, "led_process: stopped"); + return -1; +} + +int start_led_process(options_t* opt) +{ + int pipefd[2]; + int led_fd; + pid_t cpid; + + led_fd = open(opt->led_filename_, O_WRONLY); + if(led_fd < 0) { + log_printf(ERROR, "led_process: open() failed: %s", strerror(errno)); + return -2; + } + + if (pipe(pipefd) == -1) { + log_printf(ERROR, "led_process: pipe() failed: %s", strerror(errno)); + close(led_fd); + return -2; + } + + cpid = fork(); + if (cpid == -1) { + log_printf(ERROR, "led_process: fork() failed: %s", strerror(errno)); + close(pipefd[0]); + close(pipefd[1]); + close(led_fd); + return -2; + } + + if (cpid == 0) { + close(pipefd[1]); + return led_process(pipefd[0], led_fd); + } + + close(pipefd[0]); + close(led_fd); + return pipefd[1]; +} + +int main(int argc, char* argv[]) +{ + log_init(); + + options_t opt; + int ret = options_parse(&opt, argc, argv); + if(ret) { + if(ret > 0) { + fprintf(stderr, "syntax error near: %s\n\n", argv[ret]); + } + if(ret == -2) { + fprintf(stderr, "memory error on options_parse, exiting\n"); + } + if(ret == -5) { + fprintf(stderr, "syntax error: invalid baudrate\n"); + } + + if(ret != -2) + options_print_usage(); + + options_clear(&opt); + log_close(); + exit(ret); + } + string_list_element_t* tmp = opt.log_targets_.first_; + if(!tmp) { + log_add_target("syslog:3,heartbeatclient,daemon"); + } + else { + while(tmp) { + ret = log_add_target(tmp->string_); + if(ret) { + switch(ret) { + case -2: fprintf(stderr, "memory error on log_add_target, exitting\n"); break; + case -3: fprintf(stderr, "unknown log target: '%s', exitting\n", tmp->string_); break; + case -4: fprintf(stderr, "this log target is only allowed once: '%s', exitting\n", tmp->string_); break; + default: fprintf(stderr, "syntax error near: '%s', exitting\n", tmp->string_); break; + } + + options_clear(&opt); + log_close(); + exit(ret); + } + tmp = tmp->next_; + } + } + log_printf(NOTICE, "just started..."); + if(options_parse_post(&opt)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + priv_info_t priv; + if(opt.username_) + if(priv_init(&priv, opt.username_, opt.groupname_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + FILE* pid_file = NULL; + if(opt.pid_file_) { + pid_file = fopen(opt.pid_file_, "w"); + if(!pid_file) { + log_printf(WARNING, "unable to open pid file: %s", strerror(errno)); + } + } + + if(opt.chroot_dir_) + if(do_chroot(opt.chroot_dir_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + if(opt.username_) + if(priv_drop(&priv)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + if(opt.daemonize_) { + pid_t oldpid = getpid(); + daemonize(); + log_printf(INFO, "running in background now (old pid: %d)", oldpid); + } + + if(pid_file) { + pid_t pid = getpid(); + fprintf(pid_file, "%d", pid); + fclose(pid_file); + } + + int led_pipe_fd = -1; + if(opt.led_filename_) { + log_printf(NOTICE, "starting led blink process"); + led_pipe_fd = start_led_process(&opt); + if(led_pipe_fd == -1) { + options_clear(&opt); + log_close(); + exit(0); + } + if(led_pipe_fd < -1) { + options_clear(&opt); + log_close(); + exit(-1); + } + } + + int cmd_fd = 0; + int serial_fd = 0; + for(;;) { + cmd_fd = connect_command_socket(opt.command_sock_); + if(cmd_fd < 0) + ret = 3; + else { + serial_fd = open(opt.serial_dev_, O_RDWR | O_NOCTTY); + if(serial_fd < 0) + ret = 2; + else { + ret = setup_tty(serial_fd, opt.baudrate_); + if(ret) + ret = 2; + else + ret = main_loop(serial_fd, cmd_fd, led_pipe_fd, &opt); + } + } + + if(ret == 2 || ret == 3) { + if(ret == 2) + log_printf(ERROR, "%s error, trying to reopen in 5 seconds..", opt.serial_dev_); + if(ret == 3) + log_printf(ERROR, "socket error, trying to reconnect in 5 seconds.."); + + if(cmd_fd > 0) + close(cmd_fd); + if(serial_fd > 0) + close(serial_fd); + sleep(5); + } + else + break; + } + + if(cmd_fd > 0) + close(cmd_fd); + if(serial_fd > 0) + close(serial_fd); + + if(!ret) + log_printf(NOTICE, "normal shutdown"); + else if(ret < 0) + log_printf(NOTICE, "shutdown after error (code %d)", ret); + else + log_printf(NOTICE, "shutdown after signal"); + + options_clear(&opt); + log_close(); + + return ret; +} diff --git a/src/key_value_storage.c b/src/key_value_storage.c new file mode 100644 index 0000000..c6341e6 --- /dev/null +++ b/src/key_value_storage.c @@ -0,0 +1,92 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <string.h> + +#include "key_value_storage.h" + +void key_value_storage_init(key_value_storage_t* stor) +{ + if(!stor) + return; + + string_list_init(&(stor->keys_)); + string_list_init(&(stor->values_)); +} + +void key_value_storage_clear(key_value_storage_t* stor) +{ + if(!stor) + return; + + string_list_clear(&(stor->keys_)); + string_list_clear(&(stor->values_)); +} + +int key_value_storage_add(key_value_storage_t* stor, const char* key, const char* value) +{ + if(!stor || !key || !value) + return -1; + + int ret = string_list_add(&(stor->keys_), key); + if(ret!=0) + return ret; + + ret = string_list_add(&(stor->values_), value); + if(ret!=0) + return ret; + + return 0; +} + +char* key_value_storage_find(key_value_storage_t* stor, const char* key) +{ + if(!stor || !key) + return NULL; + + string_list_element_t* k = stor->keys_.first_; + string_list_element_t* v = stor->values_.first_; + while(v && k) { + if(!strcmp(k->string_, key)) + return v->string_; + + k = k->next_; + v = v->next_; + } + + return NULL; +} + +void key_value_storage_print(key_value_storage_t* stor, const char* head, const char* sep, const char* tail) +{ + if(!stor) + return; + + string_list_element_t* k = stor->keys_.first_; + string_list_element_t* v = stor->values_.first_; + while(v && k) { + printf("%s%s%s%s%s", head, k->string_, sep, v->string_, tail); + k = k->next_; + v = v->next_; + } + printf("\n"); +} diff --git a/src/key_value_storage.h b/src/key_value_storage.h new file mode 100644 index 0000000..851897f --- /dev/null +++ b/src/key_value_storage.h @@ -0,0 +1,41 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RHCTL_key_value_storage_h_INCLUDED +#define RHCTL_key_value_storage_h_INCLUDED + +#include "string_list.h" + +struct key_value_storage_struct { + string_list_t keys_; + string_list_t values_; +}; +typedef struct key_value_storage_struct key_value_storage_t; + +void key_value_storage_init(key_value_storage_t* stor); +void key_value_storage_clear(key_value_storage_t* stor); +int key_value_storage_add(key_value_storage_t* stor, const char* key, const char* value); +char* key_value_storage_find(key_value_storage_t* stor, const char* key); + +void key_value_storage_print(key_value_storage_t* stor, const char* head, const char* sep, const char* tail); + + +#endif diff --git a/src/l_cmd.c b/src/l_cmd.c new file mode 100644 index 0000000..86bc872 --- /dev/null +++ b/src/l_cmd.c @@ -0,0 +1,152 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <lua.h> +#include <lauxlib.h> + +#include <unistd.h> +#include <sys/socket.h> +#include <errno.h> + +#include "datatypes.h" + +#include "l_cmd.h" + +#include "options.h" +#include "command_queue.h" +#include "client_list.h" +#include "log.h" +#include "utils.h" + +int cmd_fd; +read_buffer_t cmd_buffer; + +static int l_cmd_getfd(lua_State *L) +{ + if(!lua_istable(L, -1)) + luaL_error(L, "can't retreive signal fd"); + + lua_pushliteral(L, "fd"); + lua_gettable(L, -2); + return 1; +} + +static int l_cmd_dirty(lua_State *L) +{ + lua_pushboolean(L, 0); + return 1; +} + +static int l_cmd_init(lua_State *L) +{ + lua_newtable(L); + lua_pushliteral(L, "fd"); + lua_pushinteger(L, cmd_fd); + lua_settable(L, -3); + lua_pushliteral(L, "getfd"); + lua_pushcfunction(L, l_cmd_getfd); + lua_settable(L, -3); + lua_pushliteral(L, "dirty"); + lua_pushcfunction(L, l_cmd_dirty); + lua_settable(L, -3); + return 1; +} + +// gets called by nonblock_recvline for every complete message +static int cmd_recv_data(lua_State* L) +{ + int ret = 0; + for(;;) { + ret = recv(cmd_fd, &cmd_buffer.buf[cmd_buffer.offset], 1, 0); + if(!ret) + return 2; + if(ret == -1 && errno == EAGAIN) + return 0; + else if(ret < 0) + break; + + if(cmd_buffer.buf[cmd_buffer.offset] == '\n') { + cmd_buffer.buf[cmd_buffer.offset] = 0; + + lua_pushstring(L, (char *)(cmd_buffer.buf)); + + ret = lua_pcall(L, 1, 1, 0); + if(ret) { + const char* err_str = luaL_checkstring(L, -1); + switch(ret) { + case LUA_ERRRUN: log_printf(ERROR, "lua_pcall(cmd_callback) runtime error: %s", err_str); break; + case LUA_ERRMEM: log_printf(ERROR, "lua_pcall(cmd_callback) malloc error: %s", err_str); break; + case LUA_ERRERR: log_printf(ERROR, "lua_pcall(cmd_callback) error at error handler function: %s", err_str); break; + } + return -1; + } + + ret = lua_tointeger(L, 1); + cmd_buffer.offset = 0; + break; + } + + cmd_buffer.offset++; + if(cmd_buffer.offset >= sizeof(cmd_buffer.buf)) { + log_printf(DEBUG, "string too long cmd_fd"); + cmd_buffer.offset = 0; + return 0; + } + } + + return ret; +} + +static int l_cmd_recv_data(lua_State* L) +{ + int ret = cmd_recv_data(L); + lua_pushinteger(L, ret); + return 1; +} + +static int l_cmd_send_string(lua_State* L) +{ + int ret = send_string(cmd_fd, luaL_checkstring(L,1)); + if (ret > 0) + do { + ret = write(cmd_fd, "\n", 1); + } while(!ret || (ret == -1 && errno == EINTR)); + + if(ret > 0) + ret = 0; + + lua_pushinteger(L, ret); + return 1; +} + +static const struct luaL_reg cmd_funcs [] = { + { "init", l_cmd_init }, + { "recv_data", l_cmd_recv_data }, + { "send_string", l_cmd_send_string }, + { NULL, NULL } +}; + + +LUALIB_API int luaopen_cmd(lua_State *L) +{ + luaL_register(L, LUA_CMDLIBNAME, cmd_funcs); + return 1; +} diff --git a/src/l_cmd.h b/src/l_cmd.h new file mode 100644 index 0000000..8c3652c --- /dev/null +++ b/src/l_cmd.h @@ -0,0 +1,32 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RHCTL_l_cmd_h_INCLUDED +#define RHCTL_l_cmd_h_INCLUDED + +#include <lua.h> + +extern int cmd_fd; + +#define LUA_CMDLIBNAME "cmd" +LUALIB_API int luaopen_cmd(lua_State *L); + +#endif diff --git a/src/l_log.c b/src/l_log.c new file mode 100644 index 0000000..4ccf18d --- /dev/null +++ b/src/l_log.c @@ -0,0 +1,120 @@ +/* + * anylike + * + * anylike is an IKEv2 Implementation written in Lua and C. It's main + * design goal is to provide anytun and uanytun or any other SATP + * implementation with a key exchange mechanism but it should also be + * possible to use anylike as key exchange daemon for IPSec security + * associations. The use of Lua guarantees that anylike is easily + * portable to many platforms including very small ones like wireless + * routers. + * + * + * Copyright (C) 2009-2010 Markus Grueneis <gimpf@anylike.org> + * Christian Pointner <equinox@anylike.org> + * + * This file is part of anylike. + * + * anylike is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * anylike is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with anylike. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <lua.h> +#include <lauxlib.h> + +#include <stdlib.h> + +#include "datatypes.h" +#include "log.h" + +#include "l_log.h" + +static int l_log_init(lua_State *L) +{ + log_init(); + return 0; +} + +static int l_log_close(lua_State *L) +{ + log_close(); + return 0; +} + +static int l_log_add_target(lua_State *L) +{ + int ret = log_add_target(luaL_checkstring(L,1)); + if(ret) { + lua_pushboolean(L, 0); + switch(ret) { + case -2: lua_pushstring(L, "memory error at log_add_target"); break; + case -3: lua_pushstring(L, "unknown log target"); break; + case -4: lua_pushstring(L, "this log target is only allowed once"); break; + default: lua_pushstring(L, "syntax error"); break; + } + return 2; + } + + lua_pushboolean(L, 1); + return 1; +} + +static int l_log_printf(lua_State *L) +{ + int numargs = lua_gettop(L); + if(numargs < 2) + return luaL_error(L, "log.printf too few arguments"); + + if(numargs > 2) { + lua_getglobal(L, "string"); + lua_pushliteral(L, "format"); + lua_gettable(L, -2); + lua_insert(L, 2); + lua_remove(L, -1); + lua_call(L, numargs - 1, 1); + } + + log_prio_t prio = luaL_checkint(L,1); + log_printf(prio, "%s", luaL_checkstring(L, 2)); + return 0; +} + +static const struct luaL_reg log_funcs [] = { + { "init", l_log_init }, + { "close", l_log_close }, + { "add_target", l_log_add_target }, + { "printf", l_log_printf }, + { NULL, NULL } +}; + + +LUALIB_API int luaopen_log(lua_State *L) +{ + luaL_register(L, LUA_LOGLIBNAME, log_funcs); + lua_pushliteral(L, "ERROR"); + lua_pushinteger(L, ERROR); + lua_settable(L, -3); + lua_pushliteral(L, "WARNING"); + lua_pushinteger(L, WARNING); + lua_settable(L, -3); + lua_pushliteral(L, "NOTICE"); + lua_pushinteger(L, NOTICE); + lua_settable(L, -3); + lua_pushliteral(L, "INFO"); + lua_pushinteger(L, INFO); + lua_settable(L, -3); + lua_pushliteral(L, "DEBUG"); + lua_pushinteger(L, DEBUG); + lua_settable(L, -3); + return 1; +} diff --git a/src/l_log.h b/src/l_log.h new file mode 100644 index 0000000..c564c62 --- /dev/null +++ b/src/l_log.h @@ -0,0 +1,40 @@ +/* + * anylike + * + * anylike is an IKEv2 Implementation written in Lua and C. It's main + * design goal is to provide anytun and uanytun or any other SATP + * implementation with a key exchange mechanism but it should also be + * possible to use anylike as key exchange daemon for IPSec security + * associations. The use of Lua guarantees that anylike is easily + * portable to many platforms including very small ones like wireless + * routers. + * + * + * Copyright (C) 2009-2010 Markus Grueneis <gimpf@anylike.org> + * Christian Pointner <equinox@anylike.org> + * + * This file is part of anylike. + * + * anylike is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * anylike is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with anylike. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ANYLIKE_l_log_h_INCLUDED +#define ANYLIKE_l_log_h_INCLUDED + +#include <lua.h> + +#define LUA_LOGLIBNAME "log" +LUALIB_API int luaopen_log(lua_State *L); + +#endif diff --git a/src/l_sig_handler.c b/src/l_sig_handler.c new file mode 100644 index 0000000..1be30ce --- /dev/null +++ b/src/l_sig_handler.c @@ -0,0 +1,99 @@ +/* + * anylike + * + * anylike is an IKEv2 Implementation written in Lua and C. It's main + * design goal is to provide anytun and uanytun or any other SATP + * implementation with a key exchange mechanism but it should also be + * possible to use anylike as key exchange daemon for IPSec security + * associations. The use of Lua guarantees that anylike is easily + * portable to many platforms including very small ones like wireless + * routers. + * + * + * Copyright (C) 2009-2010 Markus Grueneis <gimpf@anylike.org> + * Christian Pointner <equinox@anylike.org> + * + * This file is part of anylike. + * + * anylike is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * anylike is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with anylike. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <lua.h> +#include <lauxlib.h> + +#include "sig_handler.h" + +#include "l_sig_handler.h" + +static int l_signal_getfd(lua_State *L) +{ + if(!lua_istable(L, -1)) + luaL_error(L, "can't retreive signal fd"); + + lua_pushliteral(L, "fd"); + lua_gettable(L, -2); + return 1; +} + +static int l_signal_dirty(lua_State *L) +{ + lua_pushboolean(L, 0); + return 1; +} + +static int l_signal_init(lua_State *L) +{ + int sig_fd = signal_init(); + if(sig_fd < 0) + luaL_error(L, "error at signal init"); + + lua_newtable(L); + lua_pushliteral(L, "fd"); + lua_pushinteger(L, sig_fd); + lua_settable(L, -3); + lua_pushliteral(L, "getfd"); + lua_pushcfunction(L, l_signal_getfd); + lua_settable(L, -3); + lua_pushliteral(L, "dirty"); + lua_pushcfunction(L, l_signal_dirty); + lua_settable(L, -3); + return 1; +} + +static int l_signal_stop(lua_State *L) +{ + signal_stop(); + return 0; +} + +static int l_signal_handle(lua_State *L) +{ + int ret = signal_handle(); + lua_pushinteger(L, ret); + return 1; +} + +static const struct luaL_reg signal_funcs [] = { + { "init", l_signal_init }, + { "stop", l_signal_stop }, + { "handle", l_signal_handle }, + { NULL, NULL } +}; + + +LUALIB_API int luaopen_signal(lua_State *L) +{ + luaL_register(L, LUA_SIGNALLIBNAME, signal_funcs); + return 1; +} diff --git a/src/l_sig_handler.h b/src/l_sig_handler.h new file mode 100644 index 0000000..447b95e --- /dev/null +++ b/src/l_sig_handler.h @@ -0,0 +1,40 @@ +/* + * anylike + * + * anylike is an IKEv2 Implementation written in Lua and C. It's main + * design goal is to provide anytun and uanytun or any other SATP + * implementation with a key exchange mechanism but it should also be + * possible to use anylike as key exchange daemon for IPSec security + * associations. The use of Lua guarantees that anylike is easily + * portable to many platforms including very small ones like wireless + * routers. + * + * + * Copyright (C) 2009-2010 Markus Grueneis <gimpf@anylike.org> + * Christian Pointner <equinox@anylike.org> + * + * This file is part of anylike. + * + * anylike is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * anylike is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with anylike. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ANYLIKE_l_signal_handler_h_INCLUDED +#define ANYLIKE_l_signal_handler_h_INCLUDED + +#include <lua.h> + +#define LUA_SIGNALLIBNAME "signal" +LUALIB_API int luaopen_signal(lua_State *L); + +#endif diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..661f9a2 --- /dev/null +++ b/src/log.c @@ -0,0 +1,260 @@ +/* + * uAnytun + * + * uAnytun is a tiny implementation of SATP. Unlike Anytun which is a full + * featured implementation uAnytun has no support for multiple connections + * or synchronisation. It is a small single threaded implementation intended + * to act as a client on small platforms. + * The secure anycast tunneling protocol (satp) defines a protocol used + * for communication between any combination of unicast and anycast + * tunnel endpoints. It has less protocol overhead than IPSec in Tunnel + * mode and allows tunneling of every ETHER TYPE protocol (e.g. + * ethernet, ip, arp ...). satp directly includes cryptography and + * message authentication based on the methodes used by SRTP. It is + * intended to deliver a generic, scaleable and secure solution for + * tunneling and relaying of packets of any protocol. + * + * + * Copyright (C) 2007-2010 Christian Pointner <equinox@anytun.org> + * + * This file is part of uAnytun. + * + * uAnytun is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uAnytun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uAnytun. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include <ctype.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> + +#define SYSLOG_NAMES +#include <syslog.h> + +#include "log.h" + +log_t stdlog; + +#include "log_targets.h" + +const char* log_prio_to_string(log_prio_t prio) +{ + switch(prio) { + case ERROR: return "ERROR"; + case WARNING: return "WARNING"; + case NOTICE: return "NOTICE"; + case INFO: return "INFO"; + case DEBUG: return "DEBUG"; + } + return "UNKNOWN"; +} + +log_target_type_t log_target_parse_type(const char* conf) +{ + if(!conf) + return TARGET_UNKNOWN; + + if(!strncmp(conf, "syslog", 6)) return TARGET_SYSLOG; + if(!strncmp(conf, "file", 4)) return TARGET_FILE; + if(!strncmp(conf, "stdout", 6)) return TARGET_STDOUT; + if(!strncmp(conf, "stderr", 6)) return TARGET_STDERR; + + return TARGET_UNKNOWN; +} + +int log_targets_target_exists(log_targets_t* targets, log_target_type_t type) +{ + if(!targets && !targets->first_) + return 0; + + log_target_t* tmp = targets->first_; + while(tmp) { + if(tmp->type_ == type) + return 1; + tmp = tmp->next_; + } + return 0; +} + +int log_targets_add(log_targets_t* targets, const char* conf) +{ + if(!targets) + return -1; + + log_target_t* new_target = NULL; + int duplicates_allowed = 0; + switch(log_target_parse_type(conf)) { + case TARGET_SYSLOG: new_target = log_target_syslog_new(); break; + case TARGET_FILE: new_target = log_target_file_new(); duplicates_allowed = 1; break; + case TARGET_STDOUT: new_target = log_target_stdout_new(); break; + case TARGET_STDERR: new_target = log_target_stderr_new(); break; + default: return -3; + } + if(!new_target) + return -2; + + if(!duplicates_allowed && log_targets_target_exists(targets, new_target->type_)) { + free(new_target); + return -4; + } + + const char* prioptr = strchr(conf, ':'); + if(!prioptr || prioptr[1] == 0) { + free(new_target); + return -1; + } + prioptr++; + if(!isdigit(prioptr[0]) || (prioptr[1] != 0 && prioptr[1] != ',')) { + free(new_target); + return -1; + } + new_target->max_prio_ = prioptr[0] - '0'; + if(new_target->max_prio_ > 0) + new_target->enabled_ = 1; + + if(new_target->init != NULL) { + const char* confptr = NULL; + if(prioptr[1] != 0) + confptr = prioptr+2; + + int ret = (*new_target->init)(new_target, confptr); + if(ret) { + free(new_target); + return ret; + } + } + + if(new_target->open != NULL) + (*new_target->open)(new_target); + + + if(!targets->first_) { + targets->first_ = new_target; + } + else { + log_target_t* tmp = targets->first_; + while(tmp->next_) + tmp = tmp->next_; + + tmp->next_ = new_target; + } + return 0; +} + +void log_targets_log(log_targets_t* targets, log_prio_t prio, const char* msg) +{ + if(!targets) + return; + + log_target_t* tmp = targets->first_; + while(tmp) { + if(tmp->log != NULL && tmp->enabled_ && tmp->max_prio_ >= prio) + (*tmp->log)(tmp, prio, msg); + + tmp = tmp->next_; + } +} + +void log_targets_clear(log_targets_t* targets) +{ + if(!targets) + return; + + while(targets->first_) { + log_target_t* tmp = targets->first_; + targets->first_ = tmp->next_; + if(tmp->close != NULL) + (*tmp->close)(tmp); + if(tmp->clear != NULL) + (*tmp->clear)(tmp); + free(tmp); + } +} + + +void log_init() +{ + stdlog.max_prio_ = 0; + stdlog.targets_.first_ = NULL; +} + +void log_close() +{ + log_targets_clear(&stdlog.targets_); +} + +void update_max_prio() +{ + log_target_t* tmp = stdlog.targets_.first_; + while(tmp) { + if(tmp->enabled_ && tmp->max_prio_ > stdlog.max_prio_) + stdlog.max_prio_ = tmp->max_prio_; + + tmp = tmp->next_; + } +} + +int log_add_target(const char* conf) +{ + if(!conf) + return -1; + + int ret = log_targets_add(&stdlog.targets_, conf); + if(!ret) update_max_prio(); + return ret; +} + +void log_printf(log_prio_t prio, const char* fmt, ...) +{ + if(stdlog.max_prio_ < prio) + return; + + static char msg[MSG_LENGTH_MAX]; + va_list args; + + va_start(args, fmt); + vsnprintf(msg, MSG_LENGTH_MAX, fmt, args); + va_end(args); + + log_targets_log(&stdlog.targets_, prio, msg); +} + +void log_print_hex_dump(log_prio_t prio, const u_int8_t* buf, u_int32_t len) +{ + if(stdlog.max_prio_ < prio) + return; + + static char msg[MSG_LENGTH_MAX]; + + if(!buf) { + snprintf(msg, MSG_LENGTH_MAX, "(NULL)"); + } + else { + u_int32_t i; + int offset = snprintf(msg, MSG_LENGTH_MAX, "dump(%d): ", len); + if(offset < 0) + return; + char* ptr = &msg[offset]; + + for(i=0; i < len; i++) { + if(((i+1)*3) >= (MSG_LENGTH_MAX - offset)) + break; + snprintf(ptr, 3, "%02X ", buf[i]); + ptr+=3; + } + } + log_targets_log(&stdlog.targets_, prio, msg); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..e9f35dc --- /dev/null +++ b/src/log.h @@ -0,0 +1,90 @@ +/* + * uAnytun + * + * uAnytun is a tiny implementation of SATP. Unlike Anytun which is a full + * featured implementation uAnytun has no support for multiple connections + * or synchronisation. It is a small single threaded implementation intended + * to act as a client on small platforms. + * The secure anycast tunneling protocol (satp) defines a protocol used + * for communication between any combination of unicast and anycast + * tunnel endpoints. It has less protocol overhead than IPSec in Tunnel + * mode and allows tunneling of every ETHER TYPE protocol (e.g. + * ethernet, ip, arp ...). satp directly includes cryptography and + * message authentication based on the methodes used by SRTP. It is + * intended to deliver a generic, scaleable and secure solution for + * tunneling and relaying of packets of any protocol. + * + * + * Copyright (C) 2007-2010 Christian Pointner <equinox@anytun.org> + * + * This file is part of uAnytun. + * + * uAnytun is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uAnytun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uAnytun. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef UANYTUN_log_h_INCLUDED +#define UANYTUN_log_h_INCLUDED + +#define MSG_LENGTH_MAX 150 + +enum log_prio_enum { ERROR = 1, WARNING = 2, NOTICE = 3, + INFO = 4, DEBUG = 5 }; +typedef enum log_prio_enum log_prio_t; + +const char* log_prio_to_string(log_prio_t prio); + +enum log_target_type_enum { TARGET_SYSLOG , TARGET_STDOUT, TARGET_STDERR, TARGET_FILE , TARGET_UNKNOWN }; +typedef enum log_target_type_enum log_target_type_t; + +struct log_target_struct { + log_target_type_t type_; + int (*init)(struct log_target_struct* self, const char* conf); + void (*open)(struct log_target_struct* self); + void (*log)(struct log_target_struct* self, log_prio_t prio, const char* msg); + void (*close)(struct log_target_struct* self); + void (*clear)(struct log_target_struct* self); + int opened_; + int enabled_; + log_prio_t max_prio_; + void* param_; + struct log_target_struct* next_; +}; +typedef struct log_target_struct log_target_t; + + +struct log_targets_struct { + log_target_t* first_; +}; +typedef struct log_targets_struct log_targets_t; + +int log_targets_target_exists(log_targets_t* targets, log_target_type_t type); +int log_targets_add(log_targets_t* targets, const char* conf); +void log_targets_log(log_targets_t* targets, log_prio_t prio, const char* msg); +void log_targets_clear(log_targets_t* targets); + + +struct log_struct { + log_prio_t max_prio_; + log_targets_t targets_; +}; +typedef struct log_struct log_t; + +void log_init(); +void log_close(); +void update_max_prio(); +int log_add_target(const char* conf); +void log_printf(log_prio_t prio, const char* fmt, ...); +void log_print_hex_dump(log_prio_t prio, const u_int8_t* buf, u_int32_t len); + +#endif diff --git a/src/log_targets.h b/src/log_targets.h new file mode 100644 index 0000000..b6c3ae5 --- /dev/null +++ b/src/log_targets.h @@ -0,0 +1,363 @@ +/* + * uAnytun + * + * uAnytun is a tiny implementation of SATP. Unlike Anytun which is a full + * featured implementation uAnytun has no support for multiple connections + * or synchronisation. It is a small single threaded implementation intended + * to act as a client on small platforms. + * The secure anycast tunneling protocol (satp) defines a protocol used + * for communication between any combination of unicast and anycast + * tunnel endpoints. It has less protocol overhead than IPSec in Tunnel + * mode and allows tunneling of every ETHER TYPE protocol (e.g. + * ethernet, ip, arp ...). satp directly includes cryptography and + * message authentication based on the methodes used by SRTP. It is + * intended to deliver a generic, scaleable and secure solution for + * tunneling and relaying of packets of any protocol. + * + * + * Copyright (C) 2007-2010 Christian Pointner <equinox@anytun.org> + * + * This file is part of uAnytun. + * + * uAnytun is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uAnytun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uAnytun. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef UANYTUN_log_targets_h_INCLUDED +#define UANYTUN_log_targets_h_INCLUDED + +#include <time.h> + +static char* get_time_formatted() +{ + char* time_string; + time_t t = time(NULL); + if(t < 0) + time_string = "<time read error>"; + else { + time_string = ctime(&t); + if(!time_string) + time_string = "<time format error>"; + else { + char* newline = strchr(time_string, '\n'); + if(newline) + newline[0] = 0; + } + } + return time_string; +} + +enum syslog_facility_enum { USER = LOG_USER, MAIL = LOG_MAIL, + DAEMON = LOG_DAEMON, AUTH = LOG_AUTH, + SYSLOG = LOG_SYSLOG, LPR = LOG_LPR, + NEWS = LOG_NEWS, UUCP = LOG_UUCP, + CRON = LOG_CRON, AUTHPRIV = LOG_AUTHPRIV, + FTP = LOG_FTP, LOCAL0 = LOG_LOCAL0, + LOCAL1 = LOG_LOCAL1, LOCAL2 = LOG_LOCAL2, + LOCAL3 = LOG_LOCAL3, LOCAL4 = LOG_LOCAL4, + LOCAL5 = LOG_LOCAL5, LOCAL6 = LOG_LOCAL6, + LOCAL7 = LOG_LOCAL7 }; +typedef enum syslog_facility_enum syslog_facility_t; + +struct log_target_syslog_param_struct { + char* logname_; + syslog_facility_t facility_; +}; +typedef struct log_target_syslog_param_struct log_target_syslog_param_t; + +int log_target_syslog_init(log_target_t* self, const char* conf) +{ + if(!self || (conf && conf[0] == 0)) + return -1; + + self->param_ = malloc(sizeof(log_target_syslog_param_t)); + if(!self->param_) + return -2; + + char* logname; + const char* end = NULL; + if(!conf) + logname = strdup("uanytun"); + else { + end = strchr(conf, ','); + if(end) { + size_t len = (size_t)(end - conf); + if(!len) { + free(self->param_); + return -1; + } + logname = malloc(len+1); + if(logname) { + strncpy(logname, conf, len); + logname[len] = 0; + } + } + else + logname = strdup(conf); + } + + if(!logname) { + free(self->param_); + return -2; + } + ((log_target_syslog_param_t*)(self->param_))->logname_ = logname; + + if(!end) { + ((log_target_syslog_param_t*)(self->param_))->facility_ = DAEMON; + return 0; + } + + if(end[1] == 0 || end[1] == ',') { + free(logname); + free(self->param_); + return -1; + } + + const char* start = end + 1; + end = strchr(start, ','); + int i; + for(i=0;;++i) { + if(facilitynames[i].c_name == NULL) { + free(logname); + free(self->param_); + return -1; + } + + if(( end && !strncmp(start, facilitynames[i].c_name, (size_t)(end - start)) && facilitynames[i].c_name[(size_t)(end-start)] == 0) || + (!end && !strcmp(start, facilitynames[i].c_name))) { + ((log_target_syslog_param_t*)(self->param_))->facility_ = facilitynames[i].c_val; + break; + } + } + + return 0; +} + +void log_target_syslog_open(log_target_t* self) +{ + if(!self || !self->param_) + return; + + openlog(((log_target_syslog_param_t*)(self->param_))->logname_, LOG_PID, ((log_target_syslog_param_t*)(self->param_))->facility_); + self->opened_ = 1; +} + +void log_target_syslog_log(log_target_t* self, log_prio_t prio, const char* msg) +{ + if(!self || !self->param_ || !self->opened_) + return; + + syslog((prio + 2) | ((log_target_syslog_param_t*)(self->param_))->facility_, "%s", msg); +} + +void log_target_syslog_close(log_target_t* self) +{ + closelog(); + self->opened_ = 0; +} + +void log_target_syslog_clear(log_target_t* self) +{ + if(!self || !self->param_) + return; + + if(((log_target_syslog_param_t*)(self->param_))->logname_) + free(((log_target_syslog_param_t*)(self->param_))->logname_); + + free(self->param_); +} + +log_target_t* log_target_syslog_new() +{ + log_target_t* tmp = malloc(sizeof(log_target_t)); + if(!tmp) + return NULL; + + tmp->type_ = TARGET_SYSLOG; + tmp->init = &log_target_syslog_init; + tmp->open = &log_target_syslog_open; + tmp->log = &log_target_syslog_log; + tmp->close = &log_target_syslog_close; + tmp->clear = &log_target_syslog_clear; + tmp->opened_ = 0; + tmp->enabled_ = 0; + tmp->max_prio_ = NOTICE; + tmp->param_ = NULL; + tmp->next_ = NULL; + + return tmp; +} + + +struct log_target_file_param_struct { + char* logfilename_; + FILE* file_; +}; +typedef struct log_target_file_param_struct log_target_file_param_t; + +int log_target_file_init(log_target_t* self, const char* conf) +{ + if(!self || (conf && conf[0] == 0)) + return -1; + + self->param_ = malloc(sizeof(log_target_file_param_t)); + if(!self->param_) + return -2; + + char* logfilename; + if(!conf) + logfilename = strdup("uanytun.log"); + else { + const char* end = strchr(conf, ','); + if(end) { + size_t len = (size_t)(end - conf); + if(!len) { + free(self->param_); + return -1; + } + logfilename = malloc(len+1); + if(logfilename) { + strncpy(logfilename, conf, len); + logfilename[len] = 0; + } + } + else + logfilename = strdup(conf); + } + + if(!logfilename) { + free(self->param_); + return -2; + } + ((log_target_file_param_t*)(self->param_))->logfilename_ = logfilename; + ((log_target_file_param_t*)(self->param_))->file_ = NULL; + + return 0; +} + +void log_target_file_open(log_target_t* self) +{ + if(!self || !self->param_) + return; + + ((log_target_file_param_t*)(self->param_))->file_ = fopen(((log_target_file_param_t*)(self->param_))->logfilename_, "w"); + if(((log_target_file_param_t*)(self->param_))->file_) + self->opened_ = 1; +} + +void log_target_file_log(log_target_t* self, log_prio_t prio, const char* msg) +{ + if(!self || !self->param_ || !self->opened_) + return; + + fprintf(((log_target_file_param_t*)(self->param_))->file_, "%s %s: %s\n", get_time_formatted(), log_prio_to_string(prio), msg); + fflush(((log_target_file_param_t*)(self->param_))->file_); +} + +void log_target_file_close(log_target_t* self) +{ + if(!self || !self->param_) + return; + + fclose(((log_target_file_param_t*)(self->param_))->file_); + self->opened_ = 0; +} + +void log_target_file_clear(log_target_t* self) +{ + if(!self || !self->param_) + return; + + if(((log_target_file_param_t*)(self->param_))->logfilename_) + free(((log_target_file_param_t*)(self->param_))->logfilename_); + + free(self->param_); +} + + +log_target_t* log_target_file_new() +{ + log_target_t* tmp = malloc(sizeof(log_target_t)); + if(!tmp) + return NULL; + + tmp->type_ = TARGET_FILE; + tmp->init = &log_target_file_init; + tmp->open = &log_target_file_open; + tmp->log = &log_target_file_log; + tmp->close = &log_target_file_close; + tmp->clear = &log_target_file_clear; + tmp->opened_ = 0; + tmp->enabled_ = 0; + tmp->max_prio_ = NOTICE; + tmp->param_ = NULL; + tmp->next_ = NULL; + + return tmp; +} + + +void log_target_stdout_log(log_target_t* self, log_prio_t prio, const char* msg) +{ + printf("%s %s: %s\n", get_time_formatted(), log_prio_to_string(prio), msg); +} + +log_target_t* log_target_stdout_new() +{ + log_target_t* tmp = malloc(sizeof(log_target_t)); + if(!tmp) + return NULL; + + tmp->type_ = TARGET_STDOUT; + tmp->init = NULL; + tmp->open = NULL; + tmp->log = &log_target_stdout_log; + tmp->close = NULL; + tmp->clear = NULL; + tmp->opened_ = 0; + tmp->enabled_ = 0; + tmp->max_prio_ = NOTICE; + tmp->param_ = NULL; + tmp->next_ = NULL; + + return tmp; +} + + +void log_target_stderr_log(log_target_t* self, log_prio_t prio, const char* msg) +{ + fprintf(stderr, "%s %s: %s\n", get_time_formatted(), log_prio_to_string(prio), msg); +} + +log_target_t* log_target_stderr_new() +{ + log_target_t* tmp = malloc(sizeof(log_target_t)); + if(!tmp) + return NULL; + + tmp->type_ = TARGET_STDERR; + tmp->init = NULL; + tmp->open = NULL; + tmp->log = &log_target_stderr_log; + tmp->close = NULL; + tmp->clear = NULL; + tmp->opened_ = 0; + tmp->enabled_ = 0; + tmp->max_prio_ = NOTICE; + tmp->param_ = NULL; + tmp->next_ = NULL; + + return tmp; +} + +#endif diff --git a/src/luaclient.c b/src/luaclient.c new file mode 100644 index 0000000..901d509 --- /dev/null +++ b/src/luaclient.c @@ -0,0 +1,268 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> + +#include "l_log.h" +#include "l_sig_handler.h" +#include "l_cmd.h" + +#include "log.h" +#include "options.h" + +#include "daemon.h" +#include "utils.h" + + +#define LUA_MAIN_LOOP_FUNC "main_loop" + +static const luaL_Reg anylike_lualibs[] = { + {"", luaopen_base}, + {LUA_LOADLIBNAME, luaopen_package}, + {LUA_TABLIBNAME, luaopen_table}, + {LUA_STRLIBNAME, luaopen_string}, + {LUA_MATHLIBNAME, luaopen_math}, + {LUA_IOLIBNAME, luaopen_io}, + {LUA_OSLIBNAME, luaopen_os}, + {LUA_LOGLIBNAME, luaopen_log}, + {LUA_SIGNALLIBNAME, luaopen_signal}, + {LUA_CMDLIBNAME, luaopen_cmd}, + {NULL, NULL} +}; + +int init_main_loop(lua_State *L, const char* filename) +{ + const luaL_Reg *lib = anylike_lualibs; + for (; lib->func; lib++) { + lua_pushcfunction(L, lib->func); + lua_pushstring(L, lib->name); + lua_call(L, 1, 0); + } + + int ret = luaL_loadfile(L, filename); + if(ret) { + const char* err_str = luaL_checkstring(L, -1); + switch(ret) { + case LUA_ERRSYNTAX: log_printf(ERROR, "luaL_loadfile(%s) syntax error: %s", filename, err_str); break; + case LUA_ERRMEM: log_printf(ERROR, "luaL_loadfile(%s) malloc error: %s", filename, err_str); break; + case LUA_ERRFILE: log_printf(ERROR, "luaL_loadfile(%s) file access error: %s", filename, err_str); break; + default: log_printf(ERROR, "luaL_loadfile(%s) unknown error: %s", filename, err_str); break; + } + return -1; + } + + ret = lua_pcall(L, 0, 0, 0); + if(ret) { + const char* err_str = luaL_checkstring(L, -1); + switch(ret) { + case LUA_ERRRUN: log_printf(ERROR, "lua_pcall() runtime error: %s", err_str); break; + case LUA_ERRMEM: log_printf(ERROR, "lua_pcall() malloc error: %s", err_str); break; + case LUA_ERRERR: log_printf(ERROR, "lua_pcall() error at error handler function: %s", err_str); break; + } + return -1; + } + + return 0; +} + +int call_main_loop(lua_State* L, int cmd_fd, options_t* opt) +{ + lua_getglobal(L, LUA_MAIN_LOOP_FUNC); + if(!lua_isfunction(L, -1)) { + log_printf(ERROR, "there is no function '%s' inside the loaded file", LUA_MAIN_LOOP_FUNC); + return -1; + }; + + options_lua_push(opt, L); + + int ret = lua_pcall(L, 1, LUA_MULTRET, 0); + if(ret) { + const char* err_str = luaL_checkstring(L, -1); + switch(ret) { + case LUA_ERRRUN: log_printf(ERROR, "lua_pcall(%s) runtime error: %s", LUA_MAIN_LOOP_FUNC, err_str); break; + case LUA_ERRMEM: log_printf(ERROR, "lua_pcall(%s) malloc error: %s", LUA_MAIN_LOOP_FUNC, err_str); break; + case LUA_ERRERR: log_printf(ERROR, "lua_pcall(%s) error at error handler function: %s", LUA_MAIN_LOOP_FUNC, err_str); break; + } + return -1; + } + + int n = lua_gettop(L); + log_printf(DEBUG, "%s returned %d values", LUA_MAIN_LOOP_FUNC, n); + int i; + for (i = 1; i <= n; i++) + log_printf(DEBUG, "return value [%d] = '%s'", i, luaL_checkstring(L, i)); + + ret = lua_tointeger(L, 1); + return ret; +} + +int main_loop(int cmd_fd, options_t* opt) +{ + lua_State *L; + L = luaL_newstate(); + if(!L) { + log_printf(ERROR, "error creating lua state"); + return -1; + } + + int ret = init_main_loop(L, opt->lua_file_); + if(!ret) + ret = call_main_loop(L, cmd_fd, opt); + + lua_close(L); + return ret; +} + +int main(int argc, char* argv[]) +{ + log_init(); + + options_t opt; + int ret = options_parse(&opt, argc, argv); + if(ret) { + if(ret > 0) { + fprintf(stderr, "syntax error near: %s\n\n", argv[ret]); + } + if(ret == -2) { + fprintf(stderr, "memory error on options_parse, exiting\n"); + } + + if(ret != -2) + options_print_usage(); + + options_clear(&opt); + log_close(); + exit(ret); + } + string_list_element_t* tmp = opt.log_targets_.first_; + if(!tmp) { + log_add_target("syslog:3,luaclient,daemon"); + } + else { + while(tmp) { + ret = log_add_target(tmp->string_); + if(ret) { + switch(ret) { + case -2: fprintf(stderr, "memory error on log_add_target, exitting\n"); break; + case -3: fprintf(stderr, "unknown log target: '%s', exitting\n", tmp->string_); break; + case -4: fprintf(stderr, "this log target is only allowed once: '%s', exitting\n", tmp->string_); break; + default: fprintf(stderr, "syntax error near: '%s', exitting\n", tmp->string_); break; + } + + options_clear(&opt); + log_close(); + exit(ret); + } + tmp = tmp->next_; + } + } + log_printf(NOTICE, "just started..."); + if(options_parse_post(&opt)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + priv_info_t priv; + if(opt.username_) + if(priv_init(&priv, opt.username_, opt.groupname_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + FILE* pid_file = NULL; + if(opt.pid_file_) { + pid_file = fopen(opt.pid_file_, "w"); + if(!pid_file) { + log_printf(WARNING, "unable to open pid file: %s", strerror(errno)); + } + } + + if(opt.chroot_dir_) + if(do_chroot(opt.chroot_dir_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + if(opt.username_) + if(priv_drop(&priv)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + if(opt.daemonize_) { + pid_t oldpid = getpid(); + daemonize(); + log_printf(INFO, "running in background now (old pid: %d)", oldpid); + } + + if(pid_file) { + pid_t pid = getpid(); + fprintf(pid_file, "%d", pid); + fclose(pid_file); + } + + for(;;) { + // extern global variable defined in l_cmd.c + cmd_fd = connect_command_socket(opt.command_sock_); + if(cmd_fd < 0) + ret = 2; + else { + ret = main_loop(cmd_fd, &opt); + } + + if(ret == 2) { + log_printf(ERROR, "socket error, trying to reconnect in 5 seconds.."); + + if(cmd_fd > 0) + close(cmd_fd); + sleep(5); + } + else + break; + } + + if(cmd_fd > 0) + close(cmd_fd); + + if(!ret) + log_printf(NOTICE, "normal shutdown"); + else if(ret < 0) + log_printf(NOTICE, "shutdown after error (code %d)", ret); + else + log_printf(NOTICE, "shutdown after signal"); + + options_clear(&opt); + log_close(); + + return ret; +} diff --git a/src/mode-tcpserver.lua b/src/mode-tcpserver.lua new file mode 100644 index 0000000..26e42e2 --- /dev/null +++ b/src/mode-tcpserver.lua @@ -0,0 +1,175 @@ +-- +-- rhctl +-- +-- Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> +-- +-- This file is part of rhctl. +-- +-- rhctl is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- any later version. +-- +-- rhctl is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with rhctl. If not, see <http://www.gnu.org/licenses/>. +-- + +package.path = package.path .. ';/usr/share/rhctl/?.lua' + +socket = require("socket") + +current_mode = nil + +function init_server(host, port) + local server = assert(socket.tcp()) + + assert(server:setoption('reuseaddr', true)) + assert(server:bind(host, port)) + assert(server:listen(5)) + + return server +end + +local clients = {} + +function add_client(hdl) + log.printf(log.DEBUG, "new client(" .. hdl:getfd() .. ") from " .. hdl:getpeername()) + local client = {} + if current_mode then + client.buffer = current_mode .. "\n" + else + client.buffer = "" + end + client.hdl = hdl + client.getfd = function() return hdl:getfd() end + client.dirty = function() return hdl:dirty() end + table.insert(clients, client) +end + +function remove_client(c) + local idx = 0 + local found = false + for i, client in ipairs(clients) do + if client == c then + found = true + idx = i + break + end + end + + if found then + log.printf(log.DEBUG, "removing client(" .. c.hdl:getfd() .. ")") + c.hdl:close() + table.remove(clients, idx) + end +end + +function cleanup_clients() + for _, client in ipairs(clients) do + client.hdl:close() + end +end + +function clients_get_writeables() + local fds = {} + + for _, client in ipairs(clients) do + if client.buffer ~= "" then + table.insert(fds, client) + end + end + + return fds +end + +function clients_senddata(data) + for _, client in ipairs(clients) do + client.buffer = client.buffer .. data .. "\n" + end +end + +function process_cmd(message) + log.printf(log.DEBUG, "received message: '%s'", message) + + local new_mode = nil + local exps = { "Current Mode: (%a+)", "new Mode: (%a+)" } + for _, exp in ipairs(exps) do + new_mode = string.match(message, exp) + if(new_mode) then + new_mode = string.lower(new_mode) + break + end + end + + if(new_mode and new_mode ~= current_mode) then + clients_senddata(new_mode) + current_mode = new_mode + end + + return 0 +end + +function main_loop(opt) + log.printf(log.NOTICE, "main_loop started") + local sig = signal.init() + local cmdfd = cmd.init() + + local server = init_server("*", "2345") + + cmd.send_string("listen mode"); + cmd.send_string("status"); + + local return_value = 0 + while return_value == 0 do + local readable, writeable, err = socket.select({ sig , cmdfd , server , unpack(clients) }, clients_get_writeables()) + if(err) then + log.printf(log.ERROR, "select returned with error: %s", err) + return_value = -1 + else + for _, input in ipairs(readable) do + if input == sig then + return_value = signal.handle() + if(return_value == 1) then break end + elseif input == cmdfd then + return_value = cmd.recv_data(process_cmd) + if(return_value ~= 0) then break end + elseif input == server then + local client = server:accept() + if(client == nil) then + return_value =-3 + break + else + add_client(client) + end + else + if input.hdl then + -- receive is insanely stupid, therefore we must always read one byte only + local _, err = input.hdl:receive(1) + if err then + remove_client(input) + end + end + end + end + for _, output in ipairs(writeable) do + local ret = output.hdl:send(output.buffer) + if(ret == nil) then + remove_client(output) + else + output.buffer = string.sub(output.buffer, ret+1) + end + end + end + end + + server:close() + cleanup_clients() + + signal.stop() + return return_value +end diff --git a/src/mode-watch.lua b/src/mode-watch.lua new file mode 100644 index 0000000..cf716cb --- /dev/null +++ b/src/mode-watch.lua @@ -0,0 +1,119 @@ +-- +-- rhctl +-- +-- Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> +-- +-- This file is part of rhctl. +-- +-- rhctl is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- any later version. +-- +-- rhctl is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with rhctl. If not, see <http://www.gnu.org/licenses/>. +-- + +package.path = package.path .. ';/usr/share/rhctl/?.lua' + +socket = require("socket") +utils = require("utils") + +function set_master_led() + os.execute("/sbin/led.sh set master") + os.execute("/sbin/led.sh clear standby") +end + +function set_standby_led() + os.execute("/sbin/led.sh clear master") + os.execute("/sbin/led.sh set standby") +end + +function clear_leds() + os.execute("/sbin/led.sh clear master") + os.execute("/sbin/led.sh clear standby") +end + +current_mode = nil + +function process_cmd(message) + log.printf(log.DEBUG, "received message: '%s'", message) + + local new_mode = nil + local exps = { "Current Mode: (%a+)", "new Mode: (%a+)" } + for _, exp in ipairs(exps) do + new_mode = string.match(message, exp) + if(new_mode) then + new_mode = string.lower(new_mode) + if(new_mode == "master") then + set_master_led() + break + else + if(new_mode == "standby") then + set_standby_led() + break + end + end + end + end + + if(new_mode and new_mode ~= current_mode) then + log.printf(log.NOTICE, "mode is now " .. new_mode) + if(current_mode == nil) then + utils.send_mail("logs@helsinki.at", "[RHCTL] (re)started mode is now " .. new_mode, + "RHCTL just (re)started current mode is " .. new_mode) + else + utils.send_mail("logs@helsinki.at", "[RHCTL] mode changed to " .. new_mode, + "RHCTL just switched from " .. current_mode .. " to " .. new_mode) + end + current_mode = new_mode + end + + return 0 +end + +function main_loop(opt) + log.printf(log.NOTICE, "main_loop started") + local sig = signal.init() + local cmdfd = cmd.init() + + cmd.send_string("listen mode"); + cmd.send_string("status"); + + clear_leds() + + local return_value = 0 + while return_value == 0 do + local readable, _, err = socket.select({ sig , cmdfd }, nil) + if(err) then + log.printf(log.ERROR, "select returned with error: %s", err) + return_value = -1 + else + for _, input in ipairs(readable) do + if(input == sig) then + return_value = signal.handle() + if(return_value == 1) then break end + else + if(input == cmdfd) then + return_value = cmd.recv_data(process_cmd) + if(return_value ~= 0) then break end + else + log.printf(log.ERROR, "select returned invalid handle??") + return_value = -1 + break; + end + end + end + end + end + + clear_leds() + + signal.stop() + return return_value +end diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..7faa3ce --- /dev/null +++ b/src/options.c @@ -0,0 +1,556 @@ + +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include "options.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> + +#include "log.h" + +#define PARSE_BOOL_PARAM(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + VALUE = 1; + +#define PARSE_INVERSE_BOOL_PARAM(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + VALUE = 0; + +#define PARSE_INT_PARAM(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1) \ + return i; \ + VALUE = atoi(argv[i+1]); \ + argc--; \ + i++; \ + } + +#define PARSE_STRING_PARAM(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1 || argv[i+1][0] == '-') \ + return i; \ + if(VALUE) free(VALUE); \ + VALUE = strdup(argv[i+1]); \ + if(!VALUE) \ + return -2; \ + argc--; \ + i++; \ + } + +#define PARSE_STRING_PARAM_SEC(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1 || argv[i+1][0] == '-') \ + return i; \ + if(VALUE) free(VALUE); \ + VALUE = strdup(argv[i+1]); \ + if(!VALUE) \ + return -2; \ + size_t j; \ + for(j=0; j < strlen(argv[i+1]); ++j) \ + argv[i+1][j] = '#'; \ + argc--; \ + i++; \ + } + +#define PARSE_HEXSTRING_PARAM_SEC(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1 || argv[i+1][0] == '-') \ + return i; \ + int ret; \ + ret = options_parse_hex_string(argv[i+1], &VALUE); \ + if(ret > 0) \ + return i+1; \ + else if(ret < 0) \ + return ret; \ + size_t j; \ + for(j=0; j < strlen(argv[i+1]); ++j) \ + argv[i+1][j] = '#'; \ + argc--; \ + i++; \ + } + +#define PARSE_STRING_LIST(SHORT, LONG, LIST) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1 || argv[i+1][0] == '-') \ + return i; \ + int ret = string_list_add(&LIST, argv[i+1]); \ + if(ret == -2) \ + return ret; \ + else if(ret) \ + return i+1; \ + argc--; \ + i++; \ + } + +int options_parse_hex_string(const char* hex, buffer_t* buffer) +{ + if(!hex || !buffer) + return -1; + + u_int32_t hex_len = strlen(hex); + if(hex_len%2) + return 1; + + if(buffer->buf_) + free(buffer->buf_); + + buffer->length_ = hex_len/2; + buffer->buf_ = malloc(buffer->length_); + if(!buffer->buf_) { + buffer->length_ = 0; + return -2; + } + + const char* ptr = hex; + int i; + for(i=0;i<buffer->length_;++i) { + u_int32_t tmp; + sscanf(ptr, "%2X", &tmp); + buffer->buf_[i] = (u_int8_t)tmp; + ptr += 2; + } + + return 0; +} + + + +int options_parse(options_t* opt, int argc, char* argv[]) +{ + if(!opt) + return -1; + + options_default(opt); + + if(opt->progname_) + free(opt->progname_); + opt->progname_ = strdup(argv[0]); + if(!opt->progname_) + return -2; + + argc--; + + char* mode = NULL; + char* channel = NULL; + char* baudrate = NULL; + + int i; + for(i=1; argc > 0; ++i) + { + char* str = argv[i]; + argc--; + + if(!strcmp(str,"-h") || !strcmp(str,"--help")) + return -1; +#ifndef OPT_STDIOCLIENT + PARSE_INVERSE_BOOL_PARAM("-D","--nodaemonize", opt->daemonize_) + PARSE_STRING_PARAM("-u","--username", opt->username_) + PARSE_STRING_PARAM("-g","--groupname", opt->groupname_) + PARSE_STRING_PARAM("-C","--chroot", opt->chroot_dir_) + PARSE_STRING_PARAM("-P","--write-pid", opt->pid_file_) +#endif + PARSE_STRING_LIST("-L","--log", opt->log_targets_) + PARSE_STRING_PARAM("-s","--command-sock", opt->command_sock_) +#ifndef OPT_STDIOCLIENT + PARSE_STRING_PARAM("-b","--baudrate", baudrate) +#endif +#ifdef OPT_SWITCHCTL + PARSE_STRING_PARAM("-f","--config", opt->conf_file_) + PARSE_STRING_PARAM("-d","--device", opt->switch_dev_) + PARSE_STRING_PARAM("-m","--mode", mode) + PARSE_STRING_PARAM("-c","--channel", channel) +#endif +#ifdef OPT_SERIALCLIENT + PARSE_STRING_PARAM("-d","--device", opt->serial_dev_) + PARSE_STRING_PARAM("-t","--type", opt->type_) +#endif +#ifdef OPT_HEARTBEATCLIENT + PARSE_STRING_PARAM("-d","--device", opt->serial_dev_) + PARSE_INT_PARAM("-t","--timeout", opt->timeout_) + PARSE_STRING_PARAM("-l","--led", opt->led_filename_) +#endif +#ifdef OPT_LUACLIENT + PARSE_STRING_PARAM("-f","--lua-file", opt->lua_file_) +#endif + else + return i; + } + + if(mode) { + if(!strcmp(mode, "master")) + opt->mode_ = MODE_MASTER; + else if(!strcmp(mode, "standby")) + opt->mode_ = MODE_STANDBY; + else { + free(mode); + return -3; + } + free(mode); + } + + if(channel) { + if(!strcmp(channel, "main")) { + opt->channel_master_ = CHAN_MAIN; + opt->channel_standby_ = CHAN_MAIN; + } + else if(!strcmp(channel, "music")) { + opt->channel_master_ = CHAN_MUSIC; + opt->channel_standby_ = CHAN_MUSIC; + } + else { + free(channel); + return -4; + } + free(channel); + } + + if(baudrate) { + int b = atoi(baudrate); + free(baudrate); + switch(b) { + case 1200: opt->baudrate_ = B1200; break; + case 2400: opt->baudrate_ = B2400; break; + case 4800: opt->baudrate_ = B4800; break; + case 9600: opt->baudrate_ = B9600; break; + case 19200: opt->baudrate_ = B19200; break; + case 38400: opt->baudrate_ = B38400; break; + case 57600: opt->baudrate_ = B57600; break; + case 115200: opt->baudrate_ = B115200; break; + default: return -5; + } + } + + return 0; +} + +int options_parse_post(options_t* opt) +{ + if(!opt) + return -1; +// nothing to do + +#ifdef OPT_SWITCHCTL + FILE* conf_file = fopen(opt->conf_file_, "r"); + if(conf_file) { + char buf[100]; + while(fgets(buf, 100, conf_file) != NULL) { + char* tmp, *key, *value; + for(tmp = buf;*tmp == ' '; ++tmp); + if(*(key = tmp) == 0) continue; + for(;*tmp != ' ' && *tmp != 0;++tmp); + if(*tmp == 0) continue; + *tmp=0; + ++tmp; + for(;*tmp == ' ';++tmp); + if(*(value = tmp) == 0) continue; + for(;*tmp != ' ' && *tmp != 0 && *tmp != '\n';++tmp); + *tmp = 0; + + if(key_value_storage_add(&opt->alias_table_, key, value)) + return -2; + } + fclose(conf_file); + } + else { + log_printf(ERROR,"unable to open conf file (%s): %s", opt->conf_file_, strerror(errno)); + return -1; + } +#endif + + return 0; +} + +void options_default(options_t* opt) +{ + if(!opt) + return; + +#ifdef OPT_SWITCHCTL + opt->progname_ = strdup("switchctl"); +#endif +#ifdef OPT_SERIALCLIENT + opt->progname_ = strdup("serialclient"); +#endif +#ifdef OPT_STDIOCLIENT + opt->progname_ = strdup("stdioclient"); +#endif +#ifdef OPT_HEARTBEATCLIENT + opt->progname_ = strdup("heartbeatclient"); +#endif +#ifdef OPT_LUACLIENT + opt->progname_ = strdup("luaclient"); +#endif + +/* common */ + opt->daemonize_ = 1; + opt->username_ = NULL; + opt->groupname_ = NULL; + opt->chroot_dir_ = NULL; + opt->pid_file_ = NULL; + string_list_init(&opt->log_targets_); + + opt->command_sock_ = strdup("/var/run/rhctl/switchctl.sock"); + opt->baudrate_ = B19200; + +/* switchctl */ + opt->mode_ = MODE_MASTER; + opt->channel_master_ = CHAN_MAIN; + opt->channel_standby_ = CHAN_MAIN; + opt->conf_file_ = strdup("/etc/rhctl/switchctl.conf"); + opt->switch_dev_ = strdup("/dev/audioswitch"); + key_value_storage_init(&opt->alias_table_); + +/* serialclient and heartbeatclient */ + opt->serial_dev_ = strdup("/dev/ttyUSB0"); + +/* serialclient only */ + opt->type_ = NULL; + +/* heartbeatclient only */ + opt->timeout_ = 15; + opt->led_filename_ = NULL; + +/* luaclient only */ + opt->lua_file_ = strdup("/usr/share/rhctl/mode-leds.lua"); +} + +#ifdef OPT_LUACLIENT +void options_lua_push_string(lua_State* L, const int tidx, const char* key, const char* value) +{ + lua_pushstring(L, key); + lua_pushstring(L, value); + lua_settable(L, tidx); +} + +void options_lua_push_int(lua_State* L, const int tidx, const char* key, const u_int32_t value) +{ + lua_pushstring(L, key); + lua_pushinteger(L, value); + lua_settable(L, tidx); +} + +void options_lua_push_boolean(lua_State* L, const int tidx, const char* key, const u_int32_t value) +{ + lua_pushstring(L, key); + lua_pushboolean(L, value); + lua_settable(L, tidx); +} + +void options_lua_push_string_list(lua_State* L, const int tidx, string_list_t* lst) +{ + if(!lst) + return; + + string_list_element_t* tmp = lst->first_; + if(tmp) { + lua_pushstring(L, "log_targets"); + lua_newtable(L); + int i = 1; + while(tmp) { + lua_pushinteger(L, i++); + lua_pushstring(L, tmp->string_); + lua_settable(L, -3); + tmp = tmp->next_; + } + lua_settable(L, tidx); + } +} + +void options_lua_push(options_t* opt, lua_State* L) +{ + lua_newtable(L); + + options_lua_push_string(L, -3, "progname", opt->progname_); + options_lua_push_boolean(L, -3, "daemonize", opt->daemonize_); + options_lua_push_string(L, -3, "username", opt->username_); + options_lua_push_string(L, -3, "groupname", opt->groupname_); + options_lua_push_string(L, -3, "chroot_dir", opt->chroot_dir_); + options_lua_push_string(L, -3, "pid_file", opt->pid_file_); + options_lua_push_string_list(L, -3, &(opt->log_targets_)); + options_lua_push_string(L, -3, "command_sock", opt->command_sock_); + options_lua_push_string(L, -3, "lua_file", opt->lua_file_); +} +#endif + +void options_clear(options_t* opt) +{ + if(!opt) + return; + +/* common */ + if(opt->progname_) + free(opt->progname_); + if(opt->username_) + free(opt->username_); + if(opt->groupname_) + free(opt->groupname_); + if(opt->chroot_dir_) + free(opt->chroot_dir_); + if(opt->pid_file_) + free(opt->pid_file_); + string_list_clear(&opt->log_targets_); + + if(opt->command_sock_) + free(opt->command_sock_); + +/* switchctl */ + if(opt->conf_file_) + free(opt->conf_file_); + if(opt->switch_dev_) + free(opt->switch_dev_); + key_value_storage_clear(&opt->alias_table_); + +/* serialclient and heartbeatclient */ + if(opt->serial_dev_) + free(opt->serial_dev_); + +/* serialclient only */ + if(opt->type_) + free(opt->type_); + +/* heartbeatcleint only */ + if(opt->led_filename_) + free(opt->led_filename_); + +/* luaclient only */ + if(opt->lua_file_) + free(opt->lua_file_); +} + +void options_print_usage() +{ + printf("USAGE:\n"); +#ifdef OPT_SWITCHCTL + printf("switchctl\n"); +#endif +#ifdef OPT_SERIALCLIENT + printf("serialclient\n"); +#endif +#ifdef OPT_STDIOCLIENT + printf("serialclient\n"); +#endif +#ifdef OPT_HEARTBEATCLIENT + printf("heartbeatclient\n"); +#endif + printf(" [-h|--help] prints this...\n"); +#ifndef OPT_STDIOCLIENT + printf(" [-D|--nodaemonize] don't run in background\n"); + printf(" [-u|--username] <username> change to this user\n"); + printf(" [-g|--groupname] <groupname> change to this group\n"); + printf(" [-C|--chroot] <path> chroot to this directory\n"); + printf(" [-P|--write-pid] <path> write pid to this file\n"); +#endif + printf(" [-L|--log] <target>:<level>[,<param1>[,<param2>..]]\n"); + printf(" add a log target, can be invoked several times\n"); + printf(" [-s|--command-sock] <unix sock> the command socket e.g. /var/run/rhctl/switchctl.sock\n"); +#ifndef OPT_STDIOCLIENT + printf(" [-b|--baudrate] <baudrate> the baudrate of the tty to use e.g. 19200\n"); +#endif +#ifdef OPT_SWITCHCTL + printf(" [-d|--device] <tty> the tty the audio switch is connected to e.g. /dev/audioswitch\n"); + printf(" [-f|--config] <file> the configuration file e.g. /etc/rhctl/switchctl.conf\n"); + printf(" [-m|--mode] <mode> the initial mode to use e.g. master\n"); + printf(" [-c|--channel] <channel> the initial channel to use e.g. main\n"); +#endif +#ifdef OPT_SERIALCLIENT + printf(" [-d|--device] <tty> the tty to connect to e.g. /dev/ttyUSB0\n"); + printf(" [-t|--type] <type> use this client type\n"); +#endif +#ifdef OPT_HEARTBEATCLIENT + printf(" [-d|--device] <tty> the tty to connect to e.g. /dev/ttyUSB0\n"); + printf(" [-t|--timeout] <timeout> heartbeat timeout in tenths of a second e.g. 15 -> 1.5s\n"); + printf(" [-l|--led] <led filename> sysfs filename of led device to blink\n"); +#endif +#ifdef OPT_LUACLIENT + printf(" [-f|--lua-file] <file> the configuration file e.g. /usr/share/rhctl/mode-leds.lua\n"); +#endif +} + +void options_print(options_t* opt) +{ + if(!opt) + return; + + printf("progname: '%s'\n", opt->progname_); +#ifndef OPT_STDIOCLIENT + printf("daemonize: %d\n", opt->daemonize_); + printf("username: '%s'\n", opt->username_); + printf("groupname: '%s'\n", opt->groupname_); + printf("chroot_dir: '%s'\n", opt->chroot_dir_); + printf("pid_file: '%s'\n", opt->pid_file_); +#endif + printf("log_targets: \n"); + string_list_print(&opt->log_targets_, " '", "'\n"); + + printf("command_sock: '%s'\n", opt->command_sock_); + +#ifndef OPT_STDIOCLIENT + char* br; + switch(opt->baudrate_) { + case B1200: br = "1200"; break; + case B2400: br = "2400"; break; + case B4800: br = "4800"; break; + case B9600: br = "9600"; break; + case B19200: br = "19200"; break; + case B38400: br = "38400"; break; + case B57600: br = "57600"; break; + case B115200: br = "115200"; break; + default: br = "invalid"; break; + } + printf("baudrate: '%s'\n", br); +#endif + +#ifdef OPT_SWITCHCTL + printf("mode: '%s'\n", opt->mode_ == MODE_MASTER ? "master" : "standby"); + printf("channel_master: '%s'\n", opt->channel_master_ == CHAN_MAIN ? "main" : "music"); + printf("channel_standby: '%s'\n", opt->channel_standby_ == CHAN_MAIN ? "main" : "music"); + printf("conf_file: '%s'\n", opt->conf_file_); + printf("switch_dev: '%s'\n", opt->switch_dev_); + printf("alias_table: \n"); + key_value_storage_print(&opt->alias_table_, " '", "' -> '", "'\n"); +#endif + +#ifdef OPT_SERIALCLIENT + printf("serial_dev: '%s'\n", opt->serial_dev_); + printf("type: '%s'\n", opt->type_); +#endif + +#ifdef OPT_HEARTBEATCLIENT + printf("serial_dev: '%s'\n", opt->serial_dev_); + printf("timeout: %d\n", opt->timeout_); + printf("led_filename: '%s'\n", opt->led_filename_); +#endif + +#ifdef OPT_LUACLIENT + printf("lua_file: '%s'\n", opt->lua_file_); +#endif +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..450c8f2 --- /dev/null +++ b/src/options.h @@ -0,0 +1,87 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RHCTL_options_h_INCLUDED +#define RHCTL_options_h_INCLUDED + +#include <lua.h> + +#include "string_list.h" +#include "key_value_storage.h" +#include <termios.h> + +enum mode_enum { MODE_MASTER, MODE_STANDBY }; +typedef enum mode_enum switchctl_mode_t; + +enum channel_enum { CHAN_MAIN, CHAN_MUSIC }; +typedef enum channel_enum switchctl_channel_t; + +struct options_struct { +/* common */ + char* progname_; + int daemonize_; + char* username_; + char* groupname_; + char* chroot_dir_; + char* pid_file_; + string_list_t log_targets_; + + char* command_sock_; + speed_t baudrate_; + +/* switchctl */ + switchctl_mode_t mode_; + switchctl_channel_t channel_master_; + switchctl_channel_t channel_standby_; + char* conf_file_; + char* switch_dev_; + key_value_storage_t alias_table_; + +/* serialclient and heartbeatclient */ + char* serial_dev_; + +/* serialclient only */ + char* type_; + +/* heartbeatclient only */ + u_int32_t timeout_; + char* led_filename_; + +/* luaclient only */ + char* lua_file_; +}; +typedef struct options_struct options_t; + +int options_parse_hex_string(const char* hex, buffer_t* buffer); + +int options_parse(options_t* opt, int argc, char* argv[]); +int options_parse_post(options_t* opt); +void options_default(options_t* opt); +void options_lua_push_string(lua_State* L, const int tidx, const char* key, const char* value); +void options_lua_push_int(lua_State* L, const int tidx, const char* key, const u_int32_t value); +void options_lua_push_boolean(lua_State* L, const int tidx, const char* key, const u_int32_t value); +void options_lua_push_string_list(lua_State* L, const int tidx, string_list_t* lst); +void options_lua_push(options_t* opt, lua_State* L); +void options_clear(options_t* opt); +void options_print_usage(); +void options_print(options_t* opt); + +#endif diff --git a/src/serialclient.c b/src/serialclient.c new file mode 100644 index 0000000..18d62db --- /dev/null +++ b/src/serialclient.c @@ -0,0 +1,285 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "log.h" +#include "sig_handler.h" +#include "options.h" + +#include "daemon.h" +#include "utils.h" + +int process_cmd(const char* cmd, int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + return 0; +} + +int process_data(int src_fd, int dest_fd) +{ + char* buffer[100]; + int ret = read(src_fd, buffer, 100); + if(!ret) + return 2; + if(ret == -1 && errno == EAGAIN) + return 0; + if(ret < 0) + return ret; + + log_printf(DEBUG, "read %d bytes from fd (%d)", ret, src_fd); + + int len = ret; + int offset = 0; + for(;;) { + ret = write(dest_fd, &buffer[offset], len - offset); + if(ret < 0) { + if(errno != EINTR) + return ret; + + ret = 0; + } + + offset += ret; + if(offset >= len) + break; + } + log_printf(DEBUG, "wrote %d bytes to fd (%d)", offset, dest_fd); + return 0; +} + +int main_loop(int serial_fd, int cmd_fd, options_t* opt) +{ + log_printf(NOTICE, "entering main loop"); + + fd_set readfds, tmpfds; + FD_ZERO(&readfds); + FD_SET(serial_fd, &readfds); + FD_SET(cmd_fd, &readfds); + int max_fd = serial_fd > cmd_fd ? serial_fd : cmd_fd; + + int sig_fd = signal_init(); + if(sig_fd < 0) + return -1; + FD_SET(sig_fd, &readfds); + max_fd = (max_fd < sig_fd) ? sig_fd : max_fd; + + int return_value = 0; + if(opt->type_) { + char* tmp; + int len = asprintf(&tmp, "type %s\n", opt->type_); + if(len < 0) { + log_printf(ERROR, "memory error at init"); + return_value = -2; + } + else { + return_value = send_string(cmd_fd, tmp); + free(tmp); + if(return_value <= 0) { + log_printf(ERROR, "error setting client type"); + return_value = -1; + } + else { + log_printf(NOTICE, "connecting as type '%s'", opt->type_); + return_value = 0; + } + } + } + + while(!return_value) { + memcpy(&tmpfds, &readfds, sizeof(tmpfds)); + + int ret = select(max_fd+1, &tmpfds, NULL, NULL, NULL); + if(ret == -1 && errno != EINTR) { + log_printf(ERROR, "select returned with error: %s", strerror(errno)); + return_value = -1; + break; + } + if(ret == -1 || !ret) + continue; + + if(FD_ISSET(sig_fd, &tmpfds)) + if(signal_handle()) + return_value = 1; + + if(FD_ISSET(serial_fd, &tmpfds)) + return_value = process_data(serial_fd, cmd_fd); + + if(FD_ISSET(cmd_fd, &tmpfds)) { + return_value = process_data(cmd_fd, serial_fd); + if(return_value == 2) return_value = 3; + } + } + + signal_stop(); + return return_value; +} + +int main(int argc, char* argv[]) +{ + log_init(); + + options_t opt; + int ret = options_parse(&opt, argc, argv); + if(ret) { + if(ret > 0) { + fprintf(stderr, "syntax error near: %s\n\n", argv[ret]); + } + if(ret == -2) { + fprintf(stderr, "memory error on options_parse, exiting\n"); + } + if(ret == -5) { + fprintf(stderr, "syntax error: invalid baudrate\n"); + } + + if(ret != -2) + options_print_usage(); + + options_clear(&opt); + log_close(); + exit(ret); + } + string_list_element_t* tmp = opt.log_targets_.first_; + if(!tmp) { + log_add_target("syslog:3,serialclient,daemon"); + } + else { + while(tmp) { + ret = log_add_target(tmp->string_); + if(ret) { + switch(ret) { + case -2: fprintf(stderr, "memory error on log_add_target, exitting\n"); break; + case -3: fprintf(stderr, "unknown log target: '%s', exitting\n", tmp->string_); break; + case -4: fprintf(stderr, "this log target is only allowed once: '%s', exitting\n", tmp->string_); break; + default: fprintf(stderr, "syntax error near: '%s', exitting\n", tmp->string_); break; + } + + options_clear(&opt); + log_close(); + exit(ret); + } + tmp = tmp->next_; + } + } + log_printf(NOTICE, "just started..."); + if(options_parse_post(&opt)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + priv_info_t priv; + if(opt.username_) + if(priv_init(&priv, opt.username_, opt.groupname_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + FILE* pid_file = NULL; + if(opt.pid_file_) { + pid_file = fopen(opt.pid_file_, "w"); + if(!pid_file) { + log_printf(WARNING, "unable to open pid file: %s", strerror(errno)); + } + } + + if(opt.chroot_dir_) + if(do_chroot(opt.chroot_dir_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + if(opt.username_) + if(priv_drop(&priv)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + if(opt.daemonize_) { + pid_t oldpid = getpid(); + daemonize(); + log_printf(INFO, "running in background now (old pid: %d)", oldpid); + } + + if(pid_file) { + pid_t pid = getpid(); + fprintf(pid_file, "%d", pid); + fclose(pid_file); + } + + int cmd_fd = 0; + int serial_fd = 0; + for(;;) { + cmd_fd = connect_command_socket(opt.command_sock_); + if(cmd_fd < 0) + ret = 3; + else { + serial_fd = open(opt.serial_dev_, O_RDWR | O_NOCTTY); + if(serial_fd < 0) + ret = 2; + else { + ret = setup_tty(serial_fd, opt.baudrate_); + if(ret) + ret = 2; + else + ret = main_loop(serial_fd, cmd_fd, &opt); + } + } + + if(ret == 2 || ret == 3) { + if(ret == 2) + log_printf(ERROR, "%s error, trying to reopen in 5 seconds..", opt.serial_dev_); + if(ret == 3) + log_printf(ERROR, "socket error, trying to reconnect in 5 seconds.."); + + if(cmd_fd > 0) + close(cmd_fd); + if(serial_fd > 0) + close(serial_fd); + sleep(5); + } + else + break; + } + + if(cmd_fd > 0) + close(cmd_fd); + if(serial_fd > 0) + close(serial_fd); + + if(!ret) + log_printf(NOTICE, "normal shutdown"); + else if(ret < 0) + log_printf(NOTICE, "shutdown after error (code %d)", ret); + else + log_printf(NOTICE, "shutdown after signal"); + + options_clear(&opt); + log_close(); + + return ret; +} diff --git a/src/sig_handler.c b/src/sig_handler.c new file mode 100644 index 0000000..02dbcb0 --- /dev/null +++ b/src/sig_handler.c @@ -0,0 +1,163 @@ +/* + * uAnytun + * + * uAnytun is a tiny implementation of SATP. Unlike Anytun which is a full + * featured implementation uAnytun has no support for multiple connections + * or synchronisation. It is a small single threaded implementation intended + * to act as a client on small platforms. + * The secure anycast tunneling protocol (satp) defines a protocol used + * for communication between any combination of unicast and anycast + * tunnel endpoints. It has less protocol overhead than IPSec in Tunnel + * mode and allows tunneling of every ETHER TYPE protocol (e.g. + * ethernet, ip, arp ...). satp directly includes cryptography and + * message authentication based on the methodes used by SRTP. It is + * intended to deliver a generic, scaleable and secure solution for + * tunneling and relaying of packets of any protocol. + * + * + * Copyright (C) 2007-2010 Christian Pointner <equinox@anytun.org> + * + * This file is part of uAnytun. + * + * uAnytun is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uAnytun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uAnytun. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include "log.h" +#include "sig_handler.h" + +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> + +static int sig_pipe_fds[2]; +int reset_flag; + +static void sig_handler(int sig) +{ + sigset_t set; + int ret = read(sig_pipe_fds[0], &set, sizeof(sigset_t)); + if(ret != sizeof(sigset_t)) + sigemptyset(&set); + + sigaddset(&set, sig); + ret = write(sig_pipe_fds[1], &set, sizeof(sigset_t)); +} + + +int signal_init() +{ + if(pipe(sig_pipe_fds)) { + log_printf(ERROR, "signal handling init failed (pipe error: %s)", strerror(errno)); + return -1; + } + + int i; + for(i=0; i<2; ++i) { + int fd_flags = fcntl(sig_pipe_fds[i], F_GETFL); + if(fd_flags == -1) { + log_printf(ERROR, "signal handling init failed (pipe fd[%d] read flags error: %s)", i, strerror(errno)); + return -1; + } + if(fcntl(sig_pipe_fds[i], F_SETFL, fd_flags | O_NONBLOCK) == -1){ + log_printf(ERROR, "signal handling init failed (pipe fd[%d] write flags error: %s)", i, strerror(errno)); + return -1; + } + } + + struct sigaction act, ign; + act.sa_handler = sig_handler; + sigfillset(&act.sa_mask); + act.sa_flags = 0; + ign.sa_handler = SIG_IGN; + sigfillset(&ign.sa_mask); + ign.sa_flags = 0; + + if((sigaction(SIGINT, &act, NULL) < 0) || + (sigaction(SIGQUIT, &act, NULL) < 0) || + (sigaction(SIGTERM, &act, NULL) < 0) || + (sigaction(SIGHUP, &act, NULL) < 0) || + (sigaction(SIGUSR1, &act, NULL) < 0) || + (sigaction(SIGUSR2, &act, NULL) < 0) || + (sigaction(SIGCHLD, &ign, NULL) < 0) || + (sigaction(SIGPIPE, &ign, NULL) < 0)) { + + log_printf(ERROR, "signal handling init failed (sigaction error: %s)", strerror(errno)); + close(sig_pipe_fds[0]); + close(sig_pipe_fds[1]); + } + + return sig_pipe_fds[0]; +} + +int signal_handle() +{ + sigset_t set, oldset, tmpset; + + sigemptyset(&tmpset); + sigaddset(&tmpset, SIGINT); + sigaddset(&tmpset, SIGQUIT); + sigaddset(&tmpset, SIGTERM); + sigaddset(&tmpset, SIGHUP); + sigaddset(&tmpset, SIGUSR1); + sigaddset(&tmpset, SIGUSR2); + sigprocmask(SIG_BLOCK, &tmpset, &oldset); + + int ret = read(sig_pipe_fds[0], &set, sizeof(sigset_t)); + if(ret != sizeof(sigset_t)) + sigemptyset(&set); + + int return_value = 0; + int sig; + for(sig=1; sig < NSIG; ++sig) { + if(sigismember(&set, sig)) { + switch(sig) { + case SIGINT: log_printf(NOTICE, "SIG-Int caught, exitting"); return_value = 1; break; + case SIGQUIT: log_printf(NOTICE, "SIG-Quit caught, exitting"); return_value = 1; break; + case SIGTERM: log_printf(NOTICE, "SIG-Term caught, exitting"); return_value = 1; break; + case SIGHUP: log_printf(NOTICE, "SIG-Hup caught"); reset_flag = 1; break; + case SIGUSR1: log_printf(NOTICE, "SIG-Usr1 caught"); break; + case SIGUSR2: log_printf(NOTICE, "SIG-Usr2 caught"); break; + default: log_printf(WARNING, "unknown signal %d caught, ignoring", sig); break; + } + sigdelset(&set, sig); + } + } + + sigprocmask(SIG_SETMASK, &oldset, NULL); + return return_value; +} + +void signal_stop() +{ + struct sigaction act; + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + sigaction(SIGINT, &act, NULL); + sigaction(SIGQUIT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGUSR1, &act, NULL); + sigaction(SIGUSR2, &act, NULL); + sigaction(SIGPIPE, &act, NULL); + sigaction(SIGCHLD, &act, NULL); + + close(sig_pipe_fds[0]); + close(sig_pipe_fds[1]); +} diff --git a/src/sig_handler.h b/src/sig_handler.h new file mode 100644 index 0000000..3acd53a --- /dev/null +++ b/src/sig_handler.h @@ -0,0 +1,45 @@ +/* + * uAnytun + * + * uAnytun is a tiny implementation of SATP. Unlike Anytun which is a full + * featured implementation uAnytun has no support for multiple connections + * or synchronisation. It is a small single threaded implementation intended + * to act as a client on small platforms. + * The secure anycast tunneling protocol (satp) defines a protocol used + * for communication between any combination of unicast and anycast + * tunnel endpoints. It has less protocol overhead than IPSec in Tunnel + * mode and allows tunneling of every ETHER TYPE protocol (e.g. + * ethernet, ip, arp ...). satp directly includes cryptography and + * message authentication based on the methodes used by SRTP. It is + * intended to deliver a generic, scaleable and secure solution for + * tunneling and relaying of packets of any protocol. + * + * + * Copyright (C) 2007-2010 Christian Pointner <equinox@anytun.org> + * + * This file is part of uAnytun. + * + * uAnytun is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uAnytun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uAnytun. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef UANYTUN_sig_handler_h_INCLUDED +#define UANYTUN_sig_handler_h_INCLUDED + +extern int reset_flag; + +int signal_init(); +int signal_handle(); +void signal_stop(); + +#endif diff --git a/src/silence-watch.lua b/src/silence-watch.lua new file mode 100644 index 0000000..0a2ebe2 --- /dev/null +++ b/src/silence-watch.lua @@ -0,0 +1,87 @@ +-- +-- rhctl +-- +-- Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> +-- +-- This file is part of rhctl. +-- +-- rhctl is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- any later version. +-- +-- rhctl is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with rhctl. If not, see <http://www.gnu.org/licenses/>. +-- + +package.path = package.path .. ';/usr/share/rhctl/?.lua' + +socket = require("socket") +utils = require("utils") + +current_state = nil + +function process_cmd(message) + log.printf(log.DEBUG, "received message: '%s'", message) + + silence_state = string.match(message, "S0S,(%d)") + + if(silence_state and silence_state ~= current_state) then + if(silence_state == "0") then + log.printf(log.NOTICE, "seen some noise") + utils.send_mail("silence@helsinki.at", "[RHCTL] sees some noise", + "There is some noise at output 1 of the audioswitch\nCurrent State is: " .. message) + else + if (silence_state == "1") then + log.printf(log.NOTICE, "silence detected") + utils.send_mail("silence@helsinki.at", "[RHCTL] silence detected ", + "Silence detected at output 1 of the audioswitch, make some noise!!\nCurrent State is: " .. message) + end + end + current_state = silence_state + end + + return 0 +end + +function main_loop(opt) + log.printf(log.NOTICE, "main_loop started") + local sig = signal.init() + local cmdfd = cmd.init() + + cmd.send_string("listen silence"); + cmd.send_string("switch *0SS"); + + local return_value = 0 + while return_value == 0 do + local readable, _, err = socket.select({ sig , cmdfd }, nil) + if(err) then + log.printf(log.ERROR, "select returned with error: %s", err) + return_value = -1 + else + for _, input in ipairs(readable) do + if(input == sig) then + return_value = signal.handle() + if(return_value == 1) then break end + else + if(input == cmdfd) then + return_value = cmd.recv_data(process_cmd) + if(return_value ~= 0) then break end + else + log.printf(log.ERROR, "select returned invalid handle??") + return_value = -1 + break; + end + end + end + end + end + + signal.stop() + return return_value +end diff --git a/src/stdioclient.c b/src/stdioclient.c new file mode 100644 index 0000000..670758a --- /dev/null +++ b/src/stdioclient.c @@ -0,0 +1,190 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "log.h" +#include "sig_handler.h" +#include "options.h" + +#include "daemon.h" +#include "utils.h" + +int process_cmd(const char* cmd, int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + return 0; +} + +int process_data(int src_fd, int dest_fd) +{ + char* buffer[100]; + int ret = read(src_fd, buffer, 100); + if(!ret) + return 2; + if(ret == -1 && errno == EAGAIN) + return 0; + if(ret < 0) + return ret; + + log_printf(DEBUG, "read %d bytes from fd (%d)", ret, src_fd); + + int len = ret; + int offset = 0; + for(;;) { + ret = write(dest_fd, &buffer[offset], len - offset); + if(ret < 0) { + + if(errno != EINTR) + return ret; + + ret = 0; + } + + offset += ret; + if(offset+1 >= len) + break; + } + return 0; +} + +int main_loop(int cmd_fd, options_t* opt) +{ + log_printf(NOTICE, "entering main loop"); + + fd_set readfds, tmpfds; + FD_ZERO(&readfds); + FD_SET(0, &readfds); + FD_SET(cmd_fd, &readfds); + int max_fd = cmd_fd; + + int sig_fd = signal_init(); + if(sig_fd < 0) + return -1; + FD_SET(sig_fd, &readfds); + max_fd = (max_fd < sig_fd) ? sig_fd : max_fd; + + int return_value = 0; + + while(!return_value) { + memcpy(&tmpfds, &readfds, sizeof(tmpfds)); + + int ret = select(max_fd+1, &tmpfds, NULL, NULL, NULL); + if(ret == -1 && errno != EINTR) { + log_printf(ERROR, "select returned with error: %s", strerror(errno)); + return_value = -1; + break; + } + if(ret == -1 || !ret) + continue; + + if(FD_ISSET(sig_fd, &tmpfds)) + if(signal_handle()) + return_value = 1; + + if(FD_ISSET(0, &tmpfds)) + return_value = process_data(0, cmd_fd); + + if(FD_ISSET(cmd_fd, &tmpfds)) + return_value = process_data(cmd_fd, 1); + } + + signal_stop(); + return return_value; +} + +int main(int argc, char* argv[]) +{ + log_init(); + + options_t opt; + int ret = options_parse(&opt, argc, argv); + if(ret) { + if(ret > 0) { + fprintf(stderr, "syntax error near: %s\n\n", argv[ret]); + } + if(ret == -2) { + fprintf(stderr, "memory error on options_parse, exiting\n"); + } + + if(ret != -2) + options_print_usage(); + + options_clear(&opt); + log_close(); + exit(ret); + } + string_list_element_t* tmp = opt.log_targets_.first_; + if(!tmp) { + log_add_target("stderr:2"); + } + else { + while(tmp) { + ret = log_add_target(tmp->string_); + if(ret) { + switch(ret) { + case -2: fprintf(stderr, "memory error on log_add_target, exitting\n"); break; + case -3: fprintf(stderr, "unknown log target: '%s', exitting\n", tmp->string_); break; + case -4: fprintf(stderr, "this log target is only allowed once: '%s', exitting\n", tmp->string_); break; + default: fprintf(stderr, "syntax error near: '%s', exitting\n", tmp->string_); break; + } + + options_clear(&opt); + log_close(); + exit(ret); + } + tmp = tmp->next_; + } + } + log_printf(NOTICE, "just started..."); + if(options_parse_post(&opt)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + int cmd_fd = connect_command_socket(opt.command_sock_); + if(cmd_fd < 0) { + options_clear(&opt); + log_close(); + exit(-1); + } + + ret = main_loop( cmd_fd, &opt); + + close(cmd_fd); + + if(!ret) + log_printf(NOTICE, "normal shutdown"); + else if(ret < 0) + log_printf(NOTICE, "shutdown after error (code %d)", ret); + else + log_printf(NOTICE, "shutdown after signal"); + + options_clear(&opt); + log_close(); + + return ret; +} diff --git a/src/string_list.c b/src/string_list.c new file mode 100644 index 0000000..5e0ddf8 --- /dev/null +++ b/src/string_list.c @@ -0,0 +1,113 @@ +/* + * uAnytun + * + * uAnytun is a tiny implementation of SATP. Unlike Anytun which is a full + * featured implementation uAnytun has no support for multiple connections + * or synchronisation. It is a small single threaded implementation intended + * to act as a client on small platforms. + * The secure anycast tunneling protocol (satp) defines a protocol used + * for communication between any combination of unicast and anycast + * tunnel endpoints. It has less protocol overhead than IPSec in Tunnel + * mode and allows tunneling of every ETHER TYPE protocol (e.g. + * ethernet, ip, arp ...). satp directly includes cryptography and + * message authentication based on the methodes used by SRTP. It is + * intended to deliver a generic, scaleable and secure solution for + * tunneling and relaying of packets of any protocol. + * + * + * Copyright (C) 2007-2010 Christian Pointner <equinox@anytun.org> + * + * This file is part of uAnytun. + * + * uAnytun is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uAnytun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uAnytun. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "string_list.h" + +void string_list_init(string_list_t* list) +{ + if(!list) + return; + + list->first_ = NULL; +} + +void string_list_clear(string_list_t* list) +{ + if(!list) + return; + + while(list->first_) { + string_list_element_t* tmp; + tmp = list->first_; + list->first_ = tmp->next_; + if(tmp->string_) + free(tmp->string_); + free(tmp); + } +} + +int string_list_add(string_list_t* list, const char* string) +{ + if(!list) + return -1; + + if(!list->first_) { + list->first_ = malloc(sizeof(string_list_element_t)); + if(!list->first_) + return -2; + + list->first_->next_ = 0; + list->first_->string_ = strdup(string); + if(!list->first_->string_) { + free(list->first_); + list->first_ = NULL; + return -2; + } + } + else { + string_list_element_t* tmp = list->first_; + while(tmp->next_) + tmp = tmp->next_; + + tmp->next_ = malloc(sizeof(string_list_element_t)); + if(!tmp->next_) + return -2; + + tmp->next_->next_ = 0; + tmp->next_->string_ = strdup(string); + if(!tmp->next_->string_) { + free(tmp->next_); + tmp->next_ = NULL; + return -2; + } + } + return 0; +} + +void string_list_print(string_list_t* list, const char* head, const char* tail) +{ + if(!list) + return; + + string_list_element_t* tmp = list->first_; + while(tmp) { + printf("%s%s%s", head, tmp->string_, tail); + tmp = tmp->next_; + } +} diff --git a/src/string_list.h b/src/string_list.h new file mode 100644 index 0000000..9e210ae --- /dev/null +++ b/src/string_list.h @@ -0,0 +1,56 @@ +/* + * uAnytun + * + * uAnytun is a tiny implementation of SATP. Unlike Anytun which is a full + * featured implementation uAnytun has no support for multiple connections + * or synchronisation. It is a small single threaded implementation intended + * to act as a client on small platforms. + * The secure anycast tunneling protocol (satp) defines a protocol used + * for communication between any combination of unicast and anycast + * tunnel endpoints. It has less protocol overhead than IPSec in Tunnel + * mode and allows tunneling of every ETHER TYPE protocol (e.g. + * ethernet, ip, arp ...). satp directly includes cryptography and + * message authentication based on the methodes used by SRTP. It is + * intended to deliver a generic, scaleable and secure solution for + * tunneling and relaying of packets of any protocol. + * + * + * Copyright (C) 2007-2010 Christian Pointner <equinox@anytun.org> + * + * This file is part of uAnytun. + * + * uAnytun is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * uAnytun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with uAnytun. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef UANYTUN_string_list_h_INCLUDED +#define UANYTUN_string_list_h_INCLUDED + +struct string_list_element_struct { + char* string_; + struct string_list_element_struct* next_; +}; +typedef struct string_list_element_struct string_list_element_t; + +struct string_list_struct { + string_list_element_t* first_; +}; +typedef struct string_list_struct string_list_t; + +void string_list_init(string_list_t* list); +void string_list_clear(string_list_t* list); +int string_list_add(string_list_t* list, const char* string); + +void string_list_print(string_list_t* list, const char* head, const char* tail); + +#endif diff --git a/src/switchctl.c b/src/switchctl.c new file mode 100644 index 0000000..f3daab0 --- /dev/null +++ b/src/switchctl.c @@ -0,0 +1,1158 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "log.h" +#include "sig_handler.h" +#include "options.h" + +#include "command_queue.h" +#include "client_list.h" +#include "key_value_storage.h" + +#include "daemon.h" +#include "utils.h" + +struct state_struct { + switchctl_mode_t mode_; + switchctl_channel_t channel_master_; + switchctl_channel_t channel_standby_; + bool hb_state_master_; + bool hb_state_standby_; +}; +typedef struct state_struct state_t; + +state_t state_; + +int send_command(int switch_fd, cmd_t* cmd) +{ + if(!cmd) + return -1; + + char* c = NULL; + switch(cmd->cmd) { + case SWITCH: + case CHANNEL: c = cmd->param; break; + case STATUS: c = "*0SL"; break; + default: break; + } + + if(c == NULL) + return 0; + + int ret = send_string(switch_fd, c); + if(ret > 0) { + cmd_sent(cmd); + return 0; + } + + return ret; +} + +int send_response(int fd, const char* response) +{ + if(!response) + return -1; + + if(fd < 0) + return 0; + + int ret = send_string(fd, response); + do { + ret = write(fd, "\n", 1); + } while(!ret || (ret == -1 && errno == EINTR)); + + if(ret > 0) + return 0; + + return ret; +} + +void send_usage(int fd) +{ + if(fd < 0) + return; + + send_response(fd, "Usage: "); + send_response(fd, " help prints this"); + send_response(fd, " quit close connection"); + send_response(fd, " type set client type, one of: master, standby, hb_master, hb_standby"); + send_response(fd, " channel switch to channel main or music"); + send_response(fd, " client type master and standby only"); + send_response(fd, " mode switch to mode master or standby"); + send_response(fd, " heartbeat update heartbeat status for master or standby"); + send_response(fd, " status get actual status from switch"); + send_response(fd, " health get health info"); + send_response(fd, " listen register for events, no parameter for all"); + send_response(fd, " one of: request, mode, status, health, gpi, oc, relay, silence, none"); + send_response(fd, " log add line to daemons log file"); + send_response(fd, " switch send raw commands to the switch"); +} + +int process_cmd_request(const char* cmd, cmd_id_t cmd_id, const char* param, int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + client_t* c = client_find(client_lst, fd); + if(!c) { + log_printf(WARNING, "ignoring request from unknown client"); + send_response(fd, "EEE: switch: client not found in client list?!"); + return 0; + } + + char* cmd_param = NULL; + if(cmd_id == SWITCH) { + if((state_.mode_ == MODE_MASTER && c->type == STANDBY )|| + (state_.mode_ == MODE_STANDBY && c->type == MASTER )) + { + log_printf(INFO, "silently ignoring request from inactive system (%s)", c->type == MASTER ? "master" : "standby"); + return 0; + } + + if(!param) { + log_printf(INFO, "ignoring switch command without parameter"); + send_response(fd, "EEE: switch: missing parameter"); + return 0; + } + + if(param[0] == '*') { + cmd_param = strdup(param); + } + else { + const char* ch_name = NULL; + char* ch_nr = NULL; + if(!strncmp(param, "up ", 3)) { + cmd_param = strdup("*0FUii"); + ch_name = &(param[3]); + ch_nr = &(cmd_param[4]); + } + else if(!strncmp(param, "down ", 5)) { + cmd_param = strdup("*0FDii"); + ch_name = &(param[5]); + ch_nr = &(cmd_param[4]); + } + else if(!strncmp(param, "select ", 7)) { + cmd_param = strdup("*0ii1"); + ch_name = &(param[7]); + ch_nr = &(cmd_param[2]); + } + else if(!strncmp(param, "add ", 4)) { + cmd_param = strdup("*0ii3"); + ch_name = &(param[4]); + ch_nr = &(cmd_param[2]); + } + else if(!strncmp(param, "rm ", 3)) { + cmd_param = strdup("*0ii5"); + ch_name = &(param[3]); + ch_nr = &(cmd_param[2]); + } + else if(!strncmp(param, "mute ", 5)) { + cmd_param = strdup("*0iiM1"); + ch_name = &(param[5]); + ch_nr = &(cmd_param[2]); + } + else if(!strncmp(param, "select2 ", 8)) { + cmd_param = strdup("*0ii2"); + ch_name = &(param[8]); + ch_nr = &(cmd_param[2]); + } + else if(!strncmp(param, "add2 ", 5)) { + cmd_param = strdup("*0ii4"); + ch_name = &(param[5]); + ch_nr = &(cmd_param[2]); + } + else if(!strncmp(param, "rm2 ", 4)) { + cmd_param = strdup("*0ii6"); + ch_name = &(param[4]); + ch_nr = &(cmd_param[2]); + } + else if(!strncmp(param, "mute2 ", 6)) { + cmd_param = strdup("*0iiM2"); + ch_name = &(param[6]); + ch_nr = &(cmd_param[2]); + } + else { + log_printf(INFO, "ignoring invalid switch command: '%s'", param); + send_response(fd, "EEE: switch: invalid command"); + return 0; + } + char* ch_tmp = key_value_storage_find(&opt->alias_table_, ch_name); + if(!ch_tmp || ch_tmp[0] == 0 || ch_tmp[1] == 0 || ch_tmp[2] != 0) { + log_printf(ERROR, "invalid channel name or number: %s", ch_name); + send_response(fd, "EEE: switch: invalid channel name or number"); + free(cmd_param); + return 0; + } + + ch_nr[0] = ch_tmp[0]; + ch_nr[1] = ch_tmp[1]; + } + log_printf(DEBUG, "enqueing command to switch: '%s' (request was: '%s')", cmd_param, param); + } + + int ret = cmd_push(cmd_q, fd, cmd_id, cmd_param); + if(cmd_param) + free(cmd_param); + if(ret) + return ret; + + if(cmd_id == STATUS) { + char buf[3][30]; + snprintf(buf[0], 30, "Current Mode: %s", state_.mode_ == MODE_MASTER ? "Master" : "Standby"); + send_response(fd, buf[0]); + snprintf(buf[1], 30, "Master Channel: %s", state_.channel_master_ == CHAN_MAIN ? "Main" : "Music"); + send_response(fd, buf[1]); + snprintf(buf[2], 30, "Standby Channel: %s", state_.channel_standby_ == CHAN_MAIN ? "Main" : "Music"); + send_response(fd, buf[2]); + client_t* client; + int listener_cnt = 0; + for(client = client_lst; client; client = client->next) + if(client->status_listener && client->fd != fd) { + send_response(client->fd, buf[0]); + send_response(client->fd, buf[1]); + send_response(client->fd, buf[2]); + listener_cnt++; + } + log_printf(DEBUG, "sent status to %d additional listeners", listener_cnt); + } + + log_printf(NOTICE, "command: %s", cmd); + + return 0; +} + +int crossfade(const char* ch_from, const char* ch_to, int fd, cmd_t **cmd_q, options_t* opt) +{ + char* cmd_param = strdup("*0FDii*0FUii"); + + char* ch_nr_from = key_value_storage_find(&opt->alias_table_, ch_from); + if(!ch_nr_from || ch_nr_from[0] == 0 || ch_nr_from[1] == 0 || ch_nr_from[2] != 0) { + log_printf(ERROR, "invalid channel name or number: %s", ch_from); + send_response(fd, "EEE: channel: invalid channel name or number"); + free(cmd_param); + return 1; + } + + char* ch_nr_to = key_value_storage_find(&opt->alias_table_, ch_to); + if(!ch_nr_to || ch_nr_to[0] == 0 || ch_nr_to[1] == 0 || ch_nr_to[2] != 0) { + log_printf(ERROR, "invalid channel name or number: %s", ch_to); + send_response(fd, "EEE: channel: invalid channel name or number"); + free(cmd_param); + return 1; + } + + cmd_param[4] = ch_nr_from[0]; + cmd_param[5] = ch_nr_from[1]; + cmd_param[10] = ch_nr_to[0]; + cmd_param[11] = ch_nr_to[1]; + + log_printf(DEBUG, "enqueing command to switch: '%s'", cmd_param); + int ret = cmd_push(cmd_q, fd, CHANNEL, cmd_param); + free(cmd_param); + + return ret; +} + +int process_cmd_channel(const char* cmd, const char* param, int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + client_t* c = client_find(client_lst, fd); + if(!c) { + log_printf(WARNING, "ignoring request from unknown client"); + send_response(fd, "EEE: channel: client not found in client list?!"); + return 0; + } + + if(c->type != MASTER && c->type != STANDBY) { + log_printf(WARNING, "ignoring request from client of wrong type"); + send_response(fd, "EEE: channel: client type doesn't fit"); + return 0; + } + + if(!param) { + log_printf(INFO, "ignoring channel command without parameter"); + send_response(fd, "EEE: channel: missing parameter"); + return 0; + } + + switchctl_channel_t old_channel; + if(c->type == MASTER) + old_channel = state_.channel_master_; + else + old_channel = state_.channel_standby_; + + char* ch_from = NULL; + char* ch_to = NULL; + if(!strcmp(param, "main")) { + if(state_.mode_ == MODE_MASTER) { + ch_from = "master_music"; + ch_to = "master_main"; + } + else { + ch_from = "standby_music"; + ch_to = "standby_main"; + } + if(c->type == MASTER) + state_.channel_master_ = CHAN_MAIN; + else + state_.channel_standby_ = CHAN_MAIN; + } + else if(!strcmp(param, "music")) { + if(state_.mode_ == MODE_MASTER) { + ch_from = "master_main"; + ch_to = "master_music"; + } + else { + ch_from = "standby_main"; + ch_to = "standby_music"; + } + if(c->type == MASTER) + state_.channel_master_ = CHAN_MUSIC; + else + state_.channel_standby_ = CHAN_MUSIC; + } + + if((state_.mode_ == MODE_MASTER && c->type == STANDBY )|| + (state_.mode_ == MODE_STANDBY && c->type == MASTER )) + { + log_printf(INFO, "no crossfade for inactive system (%s), just updated channel info", c->type == MASTER ? "master" : "standby"); + return 0; + } + + int ret = crossfade(ch_from, ch_to, fd, cmd_q, opt); + if(ret) { + if(c->type == MASTER) + state_.channel_master_ = old_channel; + else + state_.channel_standby_ = old_channel; + + if(ret > 0) + return 0; + + return ret; + } + + log_printf(NOTICE, "command: %s", cmd); + + return 0; +} + +int change_mode(switchctl_mode_t old_mode, int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + char* ch_from = NULL; + if(old_mode == MODE_MASTER && state_.channel_master_ == CHAN_MAIN) + ch_from = "master_main"; + else if(old_mode == MODE_MASTER && state_.channel_master_ == CHAN_MUSIC) + ch_from = "master_music"; + else if(old_mode == MODE_STANDBY && state_.channel_standby_ == CHAN_MAIN) + ch_from = "standby_main"; + else if(old_mode == MODE_STANDBY && state_.channel_standby_ == CHAN_MUSIC) + ch_from = "standby_music"; + else { + state_.mode_ = old_mode; + log_printf(ERROR, "EEE: mode: old config is illegal?!"); + return 0; + } + + char* ch_to = NULL; + if(state_.mode_ == MODE_MASTER && state_.channel_master_ == CHAN_MAIN) + ch_to = "master_main"; + else if(state_.mode_ == MODE_MASTER && state_.channel_master_ == CHAN_MUSIC) + ch_to = "master_music"; + else if(state_.mode_ == MODE_STANDBY && state_.channel_standby_ == CHAN_MAIN) + ch_to = "standby_main"; + else if(state_.mode_ == MODE_STANDBY && state_.channel_standby_ == CHAN_MUSIC) + ch_to = "standby_music"; + else { + state_.mode_ = old_mode; + log_printf(ERROR, "EEE: mode: current config is illegal?!"); + return 0; + } + + int ret = crossfade(ch_from, ch_to, fd, cmd_q, opt); + if(ret) { + state_.mode_ = old_mode; + if(ret > 0) + return 0; + + return ret; + } + + char* mode_str; + int len = asprintf(&mode_str, "new Mode: %s", state_.mode_ == MODE_MASTER ? "master" : "standby"); + if(len > 0) { + log_printf(NOTICE, "%s", mode_str); + client_t* client; + int listener_cnt = 0; + for(client = client_lst; client; client = client->next) + if(client->mode_listener && client->fd != fd) { + send_response(client->fd, mode_str); + listener_cnt++; + } + free(mode_str); + log_printf(DEBUG, "sent new mode to %d additional listeners", listener_cnt); + } + + return 0; +} + +int process_cmd_mode(const char* param, int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + switchctl_mode_t old_mode = state_.mode_; + + if(param) { + if(!strncmp(param, "master", 6)) + state_.mode_ = MODE_MASTER; + else if(!strncmp(param, "standby", 7)) + state_.mode_ = MODE_STANDBY; + else { + log_printf(DEBUG, "unkown mode '%s'", param); + send_response(fd, "EEE: mode: unknown mode"); + return 0; + } + + // swap master with standby channels only when mode has changed + if(old_mode != state_.mode_) + return change_mode(old_mode, fd, cmd_q, client_lst, opt); + } + else { + log_printf(ERROR, "unable to set mode: empty parameter"); + send_response(fd, "EEE: mode: missing parameter"); + } + + return 0; +} + +int send_health_status(int fd, client_t* client_lst) +{ + bool mc, sc, hmc, hsc; + mc = sc = hmc = hsc = 0; + + client_t* client; + for(client = client_lst; client; client = client->next) { + switch(client->type) { + case MASTER: mc=1; break; + case STANDBY: sc=1; break; + case HB_MASTER: hmc=1; break; + case HB_STANDBY: hsc=1; break; + default: break; + } + } + + char buf[5][50]; + snprintf(buf[0], 50, "Health: %s", (mc && sc && hmc && hsc && state_.hb_state_master_ && state_.hb_state_standby_) ? "ok" : "error"); + snprintf(buf[1], 50, "Master: %s", (mc) ? "connected" : "offline"); + snprintf(buf[2], 50, "Standby: %s", (sc) ? "connected" : "offline"); + snprintf(buf[3], 50, "Hearbeat Master: %s, %s", (hmc) ? "connected" : "offline", (state_.hb_state_master_) ? "present" : "timeout"); + snprintf(buf[4], 50, "Hearbeat Standby: %s, %s", (hsc) ? "connected" : "offline", (state_.hb_state_standby_) ? "present" : "timeout"); + + if(fd >= 0) { + send_response(fd, buf[0]); + send_response(fd, buf[1]); + send_response(fd, buf[2]); + send_response(fd, buf[3]); + send_response(fd, buf[4]); + } + + int listener_cnt = 0; + for(client = client_lst; client; client = client->next) + if(client->health_listener && client->fd != fd) { + send_response(client->fd, buf[0]); + send_response(client->fd, buf[1]); + send_response(client->fd, buf[2]); + send_response(client->fd, buf[3]); + send_response(client->fd, buf[4]); + listener_cnt++; + } + log_printf(DEBUG, "sent health info to %d additional listeners", listener_cnt); + return 0; +} + +int update_health_status(int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + bool hmc, hsc; + hmc = hsc = 0; + + client_t* client; + for(client = client_lst; client; client = client->next) { + switch(client->type) { + case HB_MASTER: hmc=1; break; + case HB_STANDBY: hsc=1; break; + default: break; + } + } + + if(!hmc) state_.hb_state_master_ = 0; + if(!hsc) state_.hb_state_standby_ = 0; + + switchctl_mode_t old_mode = state_.mode_; + if(state_.mode_ == MODE_MASTER) { + if(!state_.hb_state_master_) { + if(state_.hb_state_standby_) { + state_.mode_ = MODE_STANDBY; + return change_mode(old_mode, fd, cmd_q, client_lst, opt); + } + } + } else { + if(!state_.hb_state_standby_) { + if(state_.hb_state_master_) { + state_.mode_ = MODE_MASTER; + return change_mode(old_mode, fd, cmd_q, client_lst, opt); + } + } + } + + send_health_status(fd, client_lst); + + return 0; +} + +void process_cmd_type(const char* param, int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + if(param) { + client_t* client = client_find(client_lst, fd); + if(client) { + if(client->type == DEFAULT) { + if(!strncmp(param, "master", 6)) { + client->type = MASTER; + client->gpi_listener = 1; + } + else if(!strncmp(param, "standby", 7)) { + client->type = STANDBY; + client->gpi_listener = 1; + } + else if(!strncmp(param, "hb_master", 9)) + client->type = HB_MASTER; + else if(!strncmp(param, "hb_standby", 10)) + client->type = HB_STANDBY; + else { + log_printf(DEBUG, "unkown client type '%s'", param); + send_response(fd, "EEE: type: unknown client type"); + return; + } + log_printf(DEBUG, "client %d type set to %s", fd, param); + update_health_status(-1, cmd_q, client_lst, opt); + } + else { + log_printf(ERROR, "unable to set client type for %d: type already set to %s", fd, client_type_tostring(client->type)); + send_response(fd, "EEE: type: type already set"); + } + } + else { + log_printf(ERROR, "unable to set client type for %d: client not found", fd); + send_response(fd, "EEE: type: client not found in client list?!"); + } + } + else { + log_printf(ERROR, "unable to set client type for %d: empty parameter", fd); + send_response(fd, "EEE: type: missing parameter"); + } +} + +int process_cmd_heartbeat(const char* param, int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + if(param) { + client_t* client = client_find(client_lst, fd); + if(client) { + switch(client->type) { + case HB_MASTER: { + state_.hb_state_master_ = (param[0] == '1') ? TRUE : FALSE; + break; + } + case HB_STANDBY: { + state_.hb_state_standby_ = (param[0] == '1') ? TRUE : FALSE; + break; + } + default: { + log_printf(ERROR, "unable to update heartbeat status: wrong client type"); + send_response(fd, "EEE: heartbeat: wrong client type"); + break; + } + } + update_health_status(-1, cmd_q, client_lst, opt); + } + else { + log_printf(ERROR, "unable to update heartbeat status: client not found"); + send_response(fd, "EEE: heartbeat: client not found in client list?!"); + } + } + else { + log_printf(ERROR, "unable to update heartbeat status: empty parameter"); + send_response(fd, "EEE: heartbeat: missing parameter"); + } + return 0; +} + +int process_cmd_health(const char* param, int fd, client_t* client_lst) +{ + return send_health_status(fd, client_lst); +} + +void process_cmd_listen(const char* param, int fd, client_t* client_lst) +{ + client_t* listener = client_find(client_lst, fd); + if(listener) { + if(!param) { + listener->request_listener = 1; + listener->mode_listener = 1; + listener->status_listener = 1; + listener->gpi_listener = 1; + listener->oc_listener = 1; + listener->relay_listener = 1; + listener->silence_listener = 1; + listener->health_listener = 1; + } + else { + if(!strncmp(param, "request", 7)) + listener->request_listener = 1; + else if(!strncmp(param, "mode", 6)) + listener->mode_listener = 1; + else if(!strncmp(param, "status", 6)) + listener->status_listener = 1; + else if(!strncmp(param, "gpi", 3)) + listener->gpi_listener = 1; + else if(!strncmp(param, "oc", 2)) + listener->oc_listener = 1; + else if(!strncmp(param, "relay", 5)) + listener->relay_listener = 1; + else if(!strncmp(param, "silence", 7)) + listener->silence_listener = 1; + else if(!strncmp(param, "health", 6)) + listener->health_listener = 1; + else if(!strncmp(param, "none", 4)) { + listener->request_listener = 0; + listener->mode_listener = 0; + listener->status_listener = 0; + listener->gpi_listener = 0; + listener->oc_listener = 0; + listener->relay_listener = 0; + listener->silence_listener = 0; + listener->health_listener = 0; + } + else { + log_printf(DEBUG, "unkown listener type '%s'", param); + send_response(fd, "EEE: listen: unkown type"); + return; + } + } + log_printf(DEBUG, "listener %d requests %s messages", fd, param ? param:"all"); + } + else { + log_printf(ERROR, "unable to add listener %d", fd); + send_response(fd, "EEE: listen: client not found in client list?!"); + } +} + +int process_cmd(const char* cmd, int fd, cmd_t **cmd_q, client_t* client_lst, options_t* opt) +{ + log_printf(DEBUG, "processing command from %d", fd); + + if(!cmd_q || !cmd) + return -1; + + cmd_id_t cmd_id; + if(!strncmp(cmd, "switch", 6)) + cmd_id = SWITCH; + else if(!strncmp(cmd, "channel", 7)) + cmd_id = CHANNEL; + else if(!strncmp(cmd, "type", 4)) + cmd_id = TYPE; + else if(!strncmp(cmd, "mode", 4)) + cmd_id = MODE; + else if(!strncmp(cmd, "heartbeat", 5)) + cmd_id = HEARTBEAT; + else if(!strncmp(cmd, "status", 6)) + cmd_id = STATUS; + else if(!strncmp(cmd, "health", 6)) + cmd_id = HEALTH; + else if(!strncmp(cmd, "log", 3)) + cmd_id = LOG; + else if(!strncmp(cmd, "listen", 6)) + cmd_id = LISTEN; + else if(!strncmp(cmd, "quit", 4)) + return 2; + else { + if(!strncmp(cmd, "help", 4)) { + send_usage(fd); + } else { + log_printf(WARNING, "unknown command '%s'", cmd); + send_response(fd, "EEE: unknown command"); + } + return 0; + } + char* param = strchr(cmd, ' '); + if(param) + param++; + + if(cmd_id == SWITCH || cmd_id == CHANNEL || cmd_id == MODE) { + char* resp; + int len = asprintf(&resp, "Request: %s", cmd); + if(len > 0) { + char* linefeed = strchr(resp, '\n'); + if(linefeed) linefeed[0] = 0; + client_t* client; + int listener_cnt = 0; + for(client = client_lst; client; client = client->next) + if(client->request_listener && client->fd != fd) { + send_response(client->fd, resp); + listener_cnt++; + } + free(resp); + log_printf(DEBUG, "sent request to %d additional listeners", listener_cnt); + } +// else silently ignore memory alloc error + } + + switch(cmd_id) { + case SWITCH: + case STATUS: { + int ret = process_cmd_request(cmd, cmd_id, param, fd, cmd_q, client_lst, opt); + if(ret) + return ret; + break; + } + case CHANNEL: { + int ret = process_cmd_channel(cmd, param, fd, cmd_q, client_lst, opt); + if(ret) + return ret; + break; + } + case TYPE: process_cmd_type(param, fd, cmd_q, client_lst, opt); break; + case MODE: { + int ret = process_cmd_mode(param, fd, cmd_q, client_lst, opt); + if(ret) + return ret; + break; + } + case HEARTBEAT: { + int ret = process_cmd_heartbeat(param, fd, cmd_q, client_lst, opt); + if(ret) + return ret; + break; + } + case HEALTH: { + int ret = process_cmd_health(param, fd, client_lst); + if(ret) + return ret; + break; + } + case LOG: { + if(param && param[0]) + log_printf(NOTICE, "ext msg: %s", param); + else + log_printf(DEBUG, "ignoring empty ext log message"); + break; + } + case LISTEN: process_cmd_listen(param, fd, client_lst); break; + } + + return 0; +} + +int nonblock_recvline(read_buffer_t* buffer, int fd, cmd_t** cmd_q, client_t* client_lst, options_t* opt) +{ + int ret = 0; + for(;;) { + ret = recv(fd, &buffer->buf[buffer->offset], 1, 0); + if(!ret) + return 2; + if(ret == -1 && errno == EAGAIN) + return 0; + else if(ret < 0) + break; + + if(buffer->buf[buffer->offset] == '\n') { + buffer->buf[buffer->offset] = 0; + ret = process_cmd((char *)(buffer->buf), fd, cmd_q, client_lst, opt); + buffer->offset = 0; + break; + } + + buffer->offset++; + if(buffer->offset >= sizeof(buffer->buf)) { + log_printf(DEBUG, "string too long (fd=%d)", fd); + buffer->offset = 0; + return 0; + } + } + + return ret; +} + +#define SEND_TO_LISTENER(STRING, LEN, FLAG) \ + if(!strncmp((char *)(buffer->buf), STRING, LEN)) { \ + client_t* client; \ + int listener_cnt = 0; \ + for(client = client_lst; client; client = client->next) \ + if(client->FLAG && client->fd != cmd_fd) { \ + send_response(client->fd, (char *)(buffer->buf)); \ + listener_cnt++; \ + } \ + log_printf(DEBUG, "sent message to %d additional listeners", listener_cnt); \ + } \ + +int process_switch(read_buffer_t* buffer, int switch_fd, cmd_t **cmd_q, client_t* client_lst) +{ + int ret = 0; + struct timeval tv; + fd_set fds; + FD_ZERO(&fds); + FD_SET(switch_fd, &fds); + + for(;;) { + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(switch_fd+1, &fds, NULL, NULL, &tv); + if(!ret) + return 0; + else if(ret < 0) + return ret; + + ret = read(switch_fd, &buffer->buf[buffer->offset], 1); + if(!ret) + return 2; + if(ret == -1 && errno == EAGAIN) + return 0; + else if(ret < 0) + break; + + if(buffer->buf[buffer->offset] == '\n') { + buffer->buf[buffer->offset] = 0; + + if(buffer->offset > 0 && buffer->buf[buffer->offset-1] == '\r') + buffer->buf[buffer->offset-1] = 0; + + if(strlen((char *)(buffer->buf))) { + log_printf(NOTICE, "switch-firmware: '%s'", (char *)(buffer->buf)); + + int cmd_fd = -1; + if(cmd_q && (*cmd_q)) { + cmd_fd = (*cmd_q)->fd; + send_response(cmd_fd, (char *)(buffer->buf)); + } + + SEND_TO_LISTENER("S0L", 3, status_listener); + SEND_TO_LISTENER("S0P", 3, gpi_listener); + SEND_TO_LISTENER("S0O", 3, oc_listener); + SEND_TO_LISTENER("S0R", 3, relay_listener); + SEND_TO_LISTENER("S0S", 3, silence_listener); + + if((!strncmp((char *)(buffer->buf), "RRR", 3)) || + (!strncmp((char *)(buffer->buf), "EEE", 3))) + cmd_pop(cmd_q); + } + + buffer->offset = 0; + return 0; + } + + buffer->offset++; + if(buffer->offset >= sizeof(buffer->buf)) { + log_printf(DEBUG, "string too long (fd=%d)", switch_fd); + buffer->offset = 0; + return 0; + } + } + + return ret; +} + +int main_loop(int switch_fd, int cmd_listen_fd, options_t* opt) +{ + log_printf(NOTICE, "entering main loop"); + + fd_set readfds, tmpfds; + FD_ZERO(&readfds); + FD_SET(switch_fd, &readfds); + FD_SET(cmd_listen_fd, &readfds); + int max_fd = switch_fd > cmd_listen_fd ? switch_fd : cmd_listen_fd; + cmd_t* cmd_q = NULL; + client_t* client_lst = NULL; + + read_buffer_t switch_buffer; + switch_buffer.offset = 0; + + int sig_fd = signal_init(); + if(sig_fd < 0) + return -1; + FD_SET(sig_fd, &readfds); + max_fd = (max_fd < sig_fd) ? sig_fd : max_fd; + + int return_value = 0; + + char* channel = "unknown"; + if(state_.mode_ == MODE_MASTER && state_.channel_master_ == CHAN_MAIN) channel = "master_main"; + else if(state_.mode_ == MODE_MASTER && state_.channel_master_ == CHAN_MUSIC) channel = "master_music"; + else if(state_.mode_ == MODE_STANDBY && state_.channel_standby_ == CHAN_MAIN) channel = "standby_main"; + else if(state_.mode_ == MODE_STANDBY && state_.channel_standby_ == CHAN_MUSIC) channel = "standby_music"; + + char* cmd_param = strdup("*0M1*0ii1"); + char* ch_nr = key_value_storage_find(&opt->alias_table_, channel); + if(!ch_nr || ch_nr[0] == 0 || ch_nr[1] == 0 || ch_nr[2] != 0) { + log_printf(ERROR, "invalid channel name or number: %s", channel); + free(cmd_param); + return_value = -1; + } + else { + cmd_param[6] = ch_nr[0]; + cmd_param[7] = ch_nr[1]; + return_value = cmd_push(&cmd_q, -1, SWITCH, cmd_param); + free(cmd_param); + send_command(switch_fd, cmd_q); + } + + struct timeval timeout; + while(!return_value) { + memcpy(&tmpfds, &readfds, sizeof(tmpfds)); + + timeout.tv_sec = 0; + timeout.tv_usec = 200000; + int ret = select(max_fd+1, &tmpfds, NULL, NULL, &timeout); + if(ret == -1 && errno != EINTR) { + log_printf(ERROR, "select returned with error: %s", strerror(errno)); + return_value = -1; + break; + } + if(ret == -1) + continue; + if(!ret) { + if(cmd_q && cmd_has_expired(*cmd_q)) { + log_printf(ERROR, "last command expired"); + cmd_pop(&cmd_q); + } + else + continue; + } + + if(FD_ISSET(sig_fd, &tmpfds)) { + if(signal_handle()) { + return_value = 1; + break; + } + } + + if(FD_ISSET(switch_fd, &tmpfds)) { + return_value = process_switch(&switch_buffer, switch_fd, &cmd_q, client_lst); + if(return_value) + break; + } + + if(FD_ISSET(cmd_listen_fd, &tmpfds)) { + int new_fd = accept(cmd_listen_fd, NULL, NULL); + if(new_fd < 0) { + log_printf(ERROR, "accept returned with error: %s", strerror(errno)); + return_value = -1; + break; + } + log_printf(DEBUG, "new command connection (fd=%d)", new_fd); + FD_SET(new_fd, &readfds); + max_fd = (max_fd < new_fd) ? new_fd : max_fd; + fcntl(new_fd, F_SETFL, O_NONBLOCK); + client_add(&client_lst, new_fd); + } + + client_t* lst = client_lst; + while(lst) { + if(FD_ISSET(lst->fd, &tmpfds)) { + return_value = nonblock_recvline(&(lst->buffer), lst->fd, &cmd_q, client_lst, opt); + if(return_value == 2) { + log_printf(DEBUG, "removing closed command connection (fd=%d)", lst->fd); + client_t* deletee = lst; + lst = lst->next; + FD_CLR(deletee->fd, &readfds); + client_remove(&client_lst, deletee->fd); + update_health_status(-1, &cmd_q, client_lst, opt); + return_value = 0; + continue; + } + if(return_value) + break; + + } + if(lst) + lst = lst->next; + } + + if(cmd_q && !cmd_q->sent) + send_command(switch_fd, cmd_q); + } + + cmd_clear(&cmd_q); + client_clear(&client_lst); + signal_stop(); + return return_value; +} + +int main(int argc, char* argv[]) +{ + log_init(); + + options_t opt; + int ret = options_parse(&opt, argc, argv); + if(ret) { + if(ret > 0) { + fprintf(stderr, "syntax error near: %s\n\n", argv[ret]); + } + if(ret == -2) { + fprintf(stderr, "memory error on options_parse, exiting\n"); + } + if(ret == -3) { + fprintf(stderr, "syntax error: mode name must be either master or standby\n"); + } + if(ret == -4) { + fprintf(stderr, "syntax error: channel name must be either main or music\n"); + } + if(ret == -5) { + fprintf(stderr, "syntax error: invalid baudrate\n"); + } + + if(ret != -2) + options_print_usage(); + + options_clear(&opt); + log_close(); + exit(ret); + } + string_list_element_t* tmp = opt.log_targets_.first_; + if(!tmp) { + log_add_target("syslog:3,switchctl,daemon"); + } + else { + while(tmp) { + ret = log_add_target(tmp->string_); + if(ret) { + switch(ret) { + case -2: fprintf(stderr, "memory error on log_add_target, exitting\n"); break; + case -3: fprintf(stderr, "unknown log target: '%s', exitting\n", tmp->string_); break; + case -4: fprintf(stderr, "this log target is only allowed once: '%s', exitting\n", tmp->string_); break; + default: fprintf(stderr, "syntax error near: '%s', exitting\n", tmp->string_); break; + } + + options_clear(&opt); + log_close(); + exit(ret); + } + tmp = tmp->next_; + } + } + log_printf(NOTICE, "just started..."); + if(options_parse_post(&opt)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + priv_info_t priv; + if(opt.username_) + if(priv_init(&priv, opt.username_, opt.groupname_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + FILE* pid_file = NULL; + if(opt.pid_file_) { + pid_file = fopen(opt.pid_file_, "w"); + if(!pid_file) { + log_printf(WARNING, "unable to open pid file: %s", strerror(errno)); + } + } + + if(opt.chroot_dir_) + if(do_chroot(opt.chroot_dir_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + if(opt.username_) + if(priv_drop(&priv)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + if(opt.daemonize_) { + pid_t oldpid = getpid(); + daemonize(); + log_printf(INFO, "running in background now (old pid: %d)", oldpid); + } + + if(pid_file) { + pid_t pid = getpid(); + fprintf(pid_file, "%d", pid); + fclose(pid_file); + } + + int cmd_listen_fd = init_command_socket(opt.command_sock_); + if(cmd_listen_fd < 0) { + options_clear(&opt); + log_close(); + exit(-1); + } + + state_.mode_ = opt.mode_; + state_.channel_master_ = opt.channel_master_; + state_.channel_standby_ = opt.channel_standby_; + state_.hb_state_master_ = FALSE; + state_.hb_state_standby_ = FALSE; + + int switch_fd = 0; + for(;;) { + switch_fd = open(opt.switch_dev_, O_RDWR | O_NOCTTY); + if(switch_fd < 0) + ret = 2; + else { + ret = setup_tty(switch_fd, opt.baudrate_); + if(ret) + ret = 2; + else + ret = main_loop(switch_fd, cmd_listen_fd, &opt); + } + + if(ret == 2) { + log_printf(ERROR, "%s error, trying to reopen in 5 seconds..", opt.switch_dev_); + if(switch_fd > 0) + close(switch_fd); + sleep(5); + } + else + break; + } + + close(cmd_listen_fd); + if(switch_fd > 0) + close(switch_fd); + + if(!ret) + log_printf(NOTICE, "normal shutdown"); + else if(ret < 0) + log_printf(NOTICE, "shutdown after error (code %d)", ret); + else + log_printf(NOTICE, "shutdown after signal"); + + options_clear(&opt); + log_close(); + + return ret; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..5ffbd5b --- /dev/null +++ b/src/utils.c @@ -0,0 +1,172 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include <sys/un.h> +#include <termios.h> +#include <unistd.h> +#include <errno.h> + +#include "log.h" + +#include "utils.h" + +int init_command_socket(const char* path) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if(fd < 0) { + log_printf(ERROR, "unable to open socket: %s", strerror(errno)); + return -1; + } + + struct sockaddr_un local; + local.sun_family = AF_UNIX; + if(sizeof(local.sun_path) <= strlen(path)) { + log_printf(ERROR, "socket path is to long (max %d)", sizeof(local.sun_path)-1); + return -1; + } + strcpy(local.sun_path, path); + unlink(local.sun_path); + int len = SUN_LEN(&local); + int ret = bind(fd, (struct sockaddr*)&local, len); + if(ret) { + log_printf(ERROR, "unable to bind to '%s': %s", local.sun_path, strerror(errno)); + return -1; + } + + ret = listen(fd, 4); + if(ret) { + log_printf(ERROR, "unable to listen on command socket: %s", local.sun_path, strerror(errno)); + return -1; + } + + log_printf(INFO, "now listening on %s for incoming commands", path); + + return fd; +} + +int connect_command_socket(const char* path) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if(fd < 0) { + log_printf(ERROR, "unable to open socket: %s", strerror(errno)); + return -1; + } + + struct sockaddr_un remote; + remote.sun_family = AF_UNIX; + if(sizeof(remote.sun_path) <= strlen(path)) { + log_printf(ERROR, "socket path is to long (max %d)", sizeof(remote.sun_path)-1); + return -1; + } + strcpy(remote.sun_path, path); + int len = SUN_LEN(&remote); + int ret = connect(fd, (struct sockaddr*)&remote, len); + if(ret) { + log_printf(ERROR, "unable to connect to '%s': %s", remote.sun_path, strerror(errno)); + return -1; + } + + return fd; +} + +int send_string(int fd, const char* string) +{ + int len = strlen(string); + int offset = 0; + int ret; + for(;;) { + ret = write(fd, &string[offset], len - offset); + if(ret < 0) { + if(errno != EINTR) + return ret; + + ret = 0; + } + + offset += ret; + if(offset+1 >= len) + break; + } + return ret; +} + +int setup_tty(int fd, speed_t speed) +{ + struct termios tmio; + + int ret = tcgetattr(fd, &tmio); + if(ret) { + log_printf(ERROR, "Error on tcgetattr(): %s", strerror(errno)); + return ret; + } + + tmio.c_iflag &= ~(INLCR | ICRNL | IGNCR | IXON | IXOFF); + tmio.c_oflag &= ~(ONLCR | OCRNL | ONOCR | ONLRET); + tmio.c_cflag |= CS8 | CLOCAL | CREAD; + tmio.c_cflag &= ~(CSTOPB | PARENB); + tmio.c_lflag &= ~(ICANON | ECHO); + tmio.c_cc[VTIME] = 0; + tmio.c_cc[VMIN] = 1; + + ret = cfsetospeed(&tmio, speed); + if(ret) { + log_printf(ERROR, "Error on cfsetospeed(): %s", strerror(errno)); + return ret; + } + + ret = cfsetispeed(&tmio, speed); + if(ret) { + log_printf(ERROR, "Error on cfsetispeed(): %s", strerror(errno)); + return ret; + } + + ret = tcsetattr(fd, TCSANOW, &tmio); + if(ret) { + log_printf(ERROR, "Error on tcsetattr(): %s", strerror(errno)); + return ret; + } + + ret = tcflush(fd, TCIFLUSH); + if(ret) { + log_printf(ERROR, "Error on tcflush(): %s", strerror(errno)); + return ret; + } + + fd_set fds; + struct timeval tv; + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec = 0; + tv.tv_usec = 50000; + for(;;) { + ret = select(fd+1, &fds, NULL, NULL, &tv); + if(ret > 0) { + char buffer[100]; + ret = read(fd, buffer, sizeof(buffer)); + } + else + break; + } + + return 0; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..f83bc84 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,35 @@ +/* + * rhctl + * + * Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of rhctl. + * + * rhctl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * rhctl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rhctl. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RHCTL_utils_h_INCLUDED +#define RHCTL_utils_h_INCLUDED + +#include "command_queue.h" +#include "client_list.h" +#include "options.h" +#include <termios.h> + +int init_command_socket(const char* path); +int connect_command_socket(const char* path); +int send_string(int fd, const char* string); +int setup_tty(int fd, speed_t speed); + +#endif diff --git a/src/utils.lua b/src/utils.lua new file mode 100644 index 0000000..6d1e3a0 --- /dev/null +++ b/src/utils.lua @@ -0,0 +1,35 @@ +-- +-- rhctl +-- +-- Copyright (C) 2009-2014 Christian Pointner <equinox@helsinki.at> +-- +-- This file is part of rhctl. +-- +-- rhctl is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- any later version. +-- +-- rhctl is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with rhctl. If not, see <http://www.gnu.org/licenses/>. +-- + +local utils = {} + +function utils.send_mail(address, subject, bodytext) + local fp = assert(io.popen("/usr/bin/msmtp " .. address, "w")) + + fp:write("Subject: " .. subject .. "\n") + fp:write("To: " .. address .. "\n") + fp:write("\n") + fp:write(bodytext) + fp:write("\n") + fp:close() +end + +return utils |