...
 
Commits (105)
include:
- project: 'iopsys/gitlab-ci-pipeline'
file: '/static-code-analysis.yml'
stages:
- static_code_analysis
- unit_test
variables:
DEBUG: 'TRUE'
SOURCE_FOLDER: "."
run_unit_test:
stage: unit_test
image: iopsys/code-analysis:0.13
allow_failure: true
script:
- "./gitlab-ci/setup.sh"
- "./gitlab-ci/unit-test.sh"
artifacts:
when: always
paths:
- unit-coverage.tar.gz
......@@ -2,10 +2,12 @@ cmake_minimum_required(VERSION 2.6)
INCLUDE(CheckFunctionExists)
PROJECT(rpcd C)
PROJECT(rpcd)
ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations)
INCLUDE_DIRECTORIES(include)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules")
OPTION(FILE_SUPPORT "File plugin support" ON)
OPTION(IWINFO_SUPPORT "libiwinfo plugin support" ON)
OPTION(RPCSYS_SUPPORT "rpc-sys plugin support" ON)
......@@ -22,6 +24,10 @@ IF(HAVE_SHADOW)
ADD_DEFINITIONS(-DHAVE_SHADOW)
ENDIF()
FIND_LIBRARY(uci NAMES uci)
FIND_LIBRARY(ubus NAMES ubus)
FIND_LIBRARY(ubox NAMES ubox)
FIND_LIBRARY(blobmsg_json NAMES blobmsg_json)
FIND_LIBRARY(json NAMES json-c json)
FIND_LIBRARY(crypt NAMES crypt)
IF(crypt STREQUAL "crypt-NOTFOUND")
......@@ -34,32 +40,66 @@ INCLUDE_DIRECTORIES(${ubus_include_dir})
FIND_PATH(ubox_include_dir libubox/blobmsg_json.h)
INCLUDE_DIRECTORIES(${ubox_include_dir})
ADD_EXECUTABLE(rpcd main.c exec.c session.c uci.c plugin.c)
TARGET_LINK_LIBRARIES(rpcd ubox ubus uci dl blobmsg_json ${json} ${crypt})
set(SOURCES
main.c
exec.c
session.c
uci.c
plugin.c
login_guard.c
uci_granular.c
)
ADD_EXECUTABLE(rpcd ${SOURCES} )
TARGET_LINK_LIBRARIES(rpcd ${ubox} ${ubus} ${uci} ${blobmsg_json} ${json} ${crypt} dl)
SET(PLUGINS "")
IF(FILE_SUPPORT)
SET(PLUGINS ${PLUGINS} file_plugin)
ADD_LIBRARY(file_plugin MODULE file.c)
TARGET_LINK_LIBRARIES(file_plugin ubox ubus)
TARGET_LINK_LIBRARIES(file_plugin ${ubox} ${ubus})
SET_TARGET_PROPERTIES(file_plugin PROPERTIES OUTPUT_NAME file PREFIX "")
ENDIF()
IF(RPCSYS_SUPPORT)
SET(PLUGINS ${PLUGINS} rpcsys_plugin)
ADD_LIBRARY(rpcsys_plugin MODULE sys.c)
TARGET_LINK_LIBRARIES(rpcsys_plugin ubox ubus)
TARGET_LINK_LIBRARIES(rpcsys_plugin ${ubox} ${ubus})
SET_TARGET_PROPERTIES(rpcsys_plugin PROPERTIES OUTPUT_NAME rpcsys PREFIX "")
ENDIF()
IF (IWINFO_SUPPORT)
FIND_LIBRARY(iwinfo NAMES iwinfo)
SET(PLUGINS ${PLUGINS} iwinfo_plugin)
ADD_LIBRARY(iwinfo_plugin MODULE iwinfo.c)
TARGET_LINK_LIBRARIES(iwinfo_plugin ubox ubus iwinfo)
TARGET_LINK_LIBRARIES(iwinfo_plugin ${ubox} ${ubus} ${iwinfo})
SET_TARGET_PROPERTIES(iwinfo_plugin PROPERTIES OUTPUT_NAME iwinfo PREFIX "")
ENDIF()
IF (CMAKE_BUILD_TYPE STREQUAL Debug)
OPTION(ENABLE_BUILD_TESTS "Build tests" ON)
OPTION(ENABLE_VALGRIND_TESTS "Build tests with valgrind" ON)
ELSE()
OPTION(ENABLE_BUILD_TESTS "Build tests" OFF)
OPTION(ENABLE_VALGRIND_TESTS "Build tests with valgrind" OFF)
ENDIF()
IF(ENABLE_BUILD_TESTS)
FIND_PACKAGE(CMocka)
if(CMOCKA_FOUND)
INCLUDE(CodeCoverage)
SET(COVERAGE_EXCLUDES '*/main.c' '/usr/include/*' '*/unit_tests_*.c' '*/functional_tests_*.c' '*/exec.c' '*/file.c' '*/login_guard.c' '*/plugin.c' '*/sys.c' ''*/session.c)
APPEND_COVERAGE_COMPILER_FLAGS()
ADD_LIBRARY(${PROJECT_NAME}-api SHARED ${SOURCES})
MESSAGE("-- Building tests")
ENABLE_TESTING()
ADD_SUBDIRECTORY(test/cmocka)
ELSE(CMOCKA_FOUND)
MESSAGE("-- CMocka not found")
ENDIF(CMOCKA_FOUND)
ENDIF(ENABLE_BUILD_TESTS)
INSTALL(TARGETS rpcd ${PLUGINS}
RUNTIME DESTINATION sbin
LIBRARY DESTINATION lib
......
FROM ubuntu:16.04
LABEL maintainer="jakob.olsson@iopsys.eu"
LABEL build="docker build -t iopsys/rpcd ."
LABEL run="docker run -d --name rpcd --privileged --rm -v ${PWD}:/opt/work -p 2222:22 -e LOCAL_USER_ID=`id -u $USER` iopsys/rpcd:latest"
LABEL exec="docker exec --user=user -it rpcd bash"
LABEL stop="docker stop rpcd"
RUN \
apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
# general tools
git \
cmake \
wget \
build-essential \
lcov \
apt-utils \
autoconf \
automake \
pkg-config \
libtool \
vim \
valgrind \
gdb \
cppcheck \
python3 \
python3-setuptools \
openssh-server \
clang-format \
sudo \
strace \
supervisor \
net-tools \
iputils-ping
# Configure OpenSSH server
RUN mkdir /var/run/sshd
RUN echo 'root:root' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -i 's/PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
# Configure gdb-dashboard
RUN \
easy_install3 pip && \
pip3 install Pygments
RUN wget -P ~ git.io/.gdbinit
RUN \
mkdir ~/.gdbinit.d && \
touch ~/.gdbinit.d/init && \
echo "dashboard -layout source" >> ~/.gdbinit.d/init && \
echo "dashboard source -style context 20" >> ~/.gdbinit.d/init && \
echo "dashboard -style syntax_highlighting 'monokai'" >> ~/.gdbinit.d/init
# Install dependent libraries
RUN \
apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
lua5.1-dev \
lua5.1 \
#libjson0 \
#libjson0-dev \
libssl-dev \
libuv1-dev \
cmocka-doc \
libcmocka-dev \
libcmocka0
# Install CMocka
#RUN \
# git clone --branch cmocka-1.1.1 git://git.cryptomilk.org/projects/cmocka.git && \
# cd cmocka && \
# mkdir build && \
# cd build && \
# cmake .. && \
# make && \
# make install
# Remove cached packages.
RUN rm -rf /var/lib/apt/lists/*
RUN mkdir /opt/dev
# Install JSON-C
RUN \
cd /opt/dev && \
git clone https://github.com/json-c/json-c.git && \
cd json-c && \
sh autogen.sh && \
./configure && \
make && \
make install && \
sudo ldconfig
# ubox
RUN \
cd /opt/dev && \
git clone git://git.openwrt.org/project/libubox.git && \
cd libubox && mkdir build && cd build && \
git checkout ecf56174da9614a0b3107d33def463eefb4d7785 && \
cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE:String="Release" .. && \
make -j2 && \
make install
# uci
RUN \
cd /opt/dev && \
git clone https://git.openwrt.org/project/uci.git && \
cd uci && \
git checkout f199b961c2970b63cc83947ad49b327b3f48f05f && \
cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE:String="Release" -DBUILD_LUA=OFF . && \
make -j2 && \
make install
# ubus
RUN \
cd /opt/dev && \
git clone https://git.openwrt.org/project/ubus.git && \
cd ubus && \
git checkout 221ce7e7ff1bd1a0c9995fa9d32f58e865f7207f && \
cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE:String="Release" -DBUILD_LUA=OFF -DBUILD_EXAMPLES=OFF . && \
make -j2 && \
make install
# rpcd
#RUN \
# cd /opt/dev && \
# git clone https://git.openwrt.org/project/rpcd.git && \
# cd rpcd && \
# git checkout cfe1e75c91bc1bac82e6caab3e652b0ebee59524 && \
# cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE:String="Release" -DIWINFO_SUPPORT=NO . && \
# make -j2 && \
# make install && \
# mkdir /usr/lib/rpcd && \
# cp file.so /usr/lib/rpcd
# json-editor
RUN \
cd /opt/dev && \
git clone https://dev.iopsys.eu/iopsys/json-editor.git && \
cd json-editor && \
git checkout 44b32937a062ec4ffc9f7355841dc94ab6efa50f && \
cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE:String="Release" . && \
make && \
make install
RUN mkdir /etc/config
WORKDIR /opt/work
# Expose ports
EXPOSE 22
# Prepare supervisor
RUN mkdir -p /var/log/supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
RUN mkdir -p /etc/config/
COPY test/files/etc/config/* /etc/config/
RUN mkdir -p /usr/share/rpcd/acl.d
COPY test/files/usr/share/rpcd/acl.d/* /usr/share/rpcd/acl.d/
# Start entrypoint
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# Copyright (c) 2012 - 2017, Lars Bilke
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# CHANGES:
#
# 2012-01-31, Lars Bilke
# - Enable Code Coverage
#
# 2013-09-17, Joakim Söderberg
# - Added support for Clang.
# - Some additional usage instructions.
#
# 2016-02-03, Lars Bilke
# - Refactored functions to use named parameters
#
# 2017-06-02, Lars Bilke
# - Merged with modified version from github.com/ufz/ogs
#
#
# USAGE:
#
# 1. Copy this file into your cmake modules path.
#
# 2. Add the following line to your CMakeLists.txt:
# include(CodeCoverage)
#
# 3. Append necessary compiler flags:
# APPEND_COVERAGE_COMPILER_FLAGS()
#
# 4. If you need to exclude additional directories from the report, specify them
# using the COVERAGE_EXCLUDES variable before calling SETUP_TARGET_FOR_COVERAGE.
# Example:
# set(COVERAGE_EXCLUDES 'dir1/*' 'dir2/*')
#
# 5. Use the functions described below to create a custom make target which
# runs your test executable and produces a code coverage report.
#
# 6. Build a Debug build:
# cmake -DCMAKE_BUILD_TYPE=Debug ..
# make
# make my_coverage_target
#
include(CMakeParseArguments)
# Check prereqs
find_program( GCOV_PATH gcov )
find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl)
find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat )
find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
find_program( SIMPLE_PYTHON_EXECUTABLE python )
if(NOT GCOV_PATH)
message(FATAL_ERROR "gcov not found! Aborting...")
endif() # NOT GCOV_PATH
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3)
message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
endif()
elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
endif()
set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage"
CACHE INTERNAL "")
set(CMAKE_CXX_FLAGS_COVERAGE
${COVERAGE_COMPILER_FLAGS}
CACHE STRING "Flags used by the C++ compiler during coverage builds."
FORCE )
set(CMAKE_C_FLAGS_COVERAGE
${COVERAGE_COMPILER_FLAGS}
CACHE STRING "Flags used by the C compiler during coverage builds."
FORCE )
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
""
CACHE STRING "Flags used for linking binaries during coverage builds."
FORCE )
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
""
CACHE STRING "Flags used by the shared libraries linker during coverage builds."
FORCE )
mark_as_advanced(
CMAKE_CXX_FLAGS_COVERAGE
CMAKE_C_FLAGS_COVERAGE
CMAKE_EXE_LINKER_FLAGS_COVERAGE
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
link_libraries(gcov)
else()
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
endif()
# Defines a target for running and collection code coverage information
# Builds dependencies, runs the given executable and outputs reports.
# NOTE! The executable should always have a ZERO as exit code otherwise
# the coverage generation will not complete.
#
# SETUP_TARGET_FOR_COVERAGE(
# NAME testrunner_coverage # New target name
# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
# DEPENDENCIES testrunner # Dependencies to build first
# )
function(SETUP_TARGET_FOR_COVERAGE)
set(options NONE)
set(oneValueArgs NAME)
set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT LCOV_PATH)
message(FATAL_ERROR "lcov not found! Aborting...")
endif() # NOT LCOV_PATH
if(NOT GENHTML_PATH)
message(FATAL_ERROR "genhtml not found! Aborting...")
endif() # NOT GENHTML_PATH
# Setup target
add_custom_target(${Coverage_NAME}
# Cleanup lcov
COMMAND ${LCOV_PATH} --rc lcov_branch_coverage=1 --directory . --zerocounters
# Create baseline to make sure untouched files show up in the report
COMMAND ${LCOV_PATH} --rc lcov_branch_coverage=1 -c -i -d . -o ${Coverage_NAME}.base
# Run tests
COMMAND ${Coverage_EXECUTABLE}
# Capturing lcov counters and generating report
COMMAND ${LCOV_PATH} --rc lcov_branch_coverage=1 --directory . --capture --output-file ${Coverage_NAME}.info
# add baseline counters
COMMAND ${LCOV_PATH} --rc lcov_branch_coverage=1 -a ${Coverage_NAME}.base -a ${Coverage_NAME}.info --output-file ${Coverage_NAME}.total
COMMAND ${LCOV_PATH} --rc lcov_branch_coverage=1 --remove ${Coverage_NAME}.total ${COVERAGE_EXCLUDES} --output-file ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
COMMAND ${GENHTML_PATH} --legend --branch-coverage -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
COMMAND ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.info ${Coverage_NAME}.total ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
)
# Show info where to find the report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
)
endfunction() # SETUP_TARGET_FOR_COVERAGE
# Defines a target for running and collection code coverage information
# Builds dependencies, runs the given executable and outputs reports.
# NOTE! The executable should always have a ZERO as exit code otherwise
# the coverage generation will not complete.
#
# SETUP_TARGET_FOR_COVERAGE_COBERTURA(
# NAME ctest_coverage # New target name
# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
# DEPENDENCIES executable_target # Dependencies to build first
# )
function(SETUP_TARGET_FOR_COVERAGE_COBERTURA)
set(options NONE)
set(oneValueArgs NAME)
set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT SIMPLE_PYTHON_EXECUTABLE)
message(FATAL_ERROR "python not found! Aborting...")
endif() # NOT SIMPLE_PYTHON_EXECUTABLE
if(NOT GCOVR_PATH)
message(FATAL_ERROR "gcovr not found! Aborting...")
endif() # NOT GCOVR_PATH
# Combine excludes to several -e arguments
set(COBERTURA_EXCLUDES "")
foreach(EXCLUDE ${COVERAGE_EXCLUDES})
set(COBERTURA_EXCLUDES "-e ${EXCLUDE} ${COBERTURA_EXCLUDES}")
endforeach()
add_custom_target(${Coverage_NAME}
# Run tests
${Coverage_EXECUTABLE}
# Running gcovr
COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} ${COBERTURA_EXCLUDES}
-o ${Coverage_NAME}.xml
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
COMMENT "Running gcovr to produce Cobertura code coverage report."
)
# Show info where to find the report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml."
)
endfunction() # SETUP_TARGET_FOR_COVERAGE_COBERTURA
function(APPEND_COVERAGE_COMPILER_FLAGS)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
endfunction() # APPEND_COVERAGE_COMPILER_FLAGS
\ No newline at end of file
# CMOCKA_FOUND - System has CMocka
# CMOCKA_INCLUDE_DIRS - The CMocka include directories
# CMOCKA_LIBRARIES - The libraries needed to use CMocka
# CMOCKA_DEFINITIONS - Compiler switches required for using CMocka
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(PC_CMOCKA QUIET cmocka)
set(CMOCKA_DEFINITIONS ${PC_CMOCKA_CFLAGS_OTHER})
endif()
find_path(CMOCKA_INCLUDE_DIR cmocka.h
HINTS ${PC_CMOCKA_INCLUDEDIR} ${PC_CMOCKA_INCLUDE_DIRS}
PATH_SUFFIXES cmocka)
find_library(CMOCKA_LIBRARY NAMES cmocka
HINTS ${PC_CMOCKA_LIBDIR} ${PC_CMOCKA_LIBRARY_DIRS})
set(CMOCKA_LIBRARIES ${CMOCKA_LIBRARY})
set(CMOCKA_INCLUDE_DIRS ${CMOCKA_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(cmocka DEFAULT_MSG
CMOCKA_LIBRARY CMOCKA_INCLUDE_DIR)
mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARY)
#!/bin/bash
# Create user
USER_ID=${LOCAL_USER_ID:-9001}
useradd --shell /bin/bash -u $USER_ID -G sudo -o -c "" -m user
adduser --disabled-password user
export HOME=/home/user
echo "user ALL = NOPASSWD : ALL" >> /etc/sudoers
# Configure OpenSSH to allow login without password
sed -i -re 's/^user:[^:]+:/user::/' /etc/passwd /etc/shadow
pam_config="auth [success=1 default=ignore] pam_unix.so nullok\nauth requisite pam_deny.so\nauth required pam_permit.so"
sed -i "s/@include common-auth/$pam_config/" /etc/pam.d/sshd
# Start supervisor
/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf -l /var/log/supervisord.log -j /var/run/supervisord.pid
......@@ -131,13 +131,13 @@ rpc_exec_reply(struct rpc_exec_context *c, int rv)
rpc_ustream_to_blobmsg(&c->blob, &c->opipe.stream, "stdout");
rpc_ustream_to_blobmsg(&c->blob, &c->epipe.stream, "stderr");
}
}
if (c->finish_cb)
rv = c->finish_cb(&c->blob, c->stat, c->priv);
if (c->finish_cb)
rv = c->finish_cb(&c->blob, c->stat, c->priv);
if (rv == UBUS_STATUS_OK)
ubus_send_reply(c->context, &c->request, c->blob.head);
}
if (rv == UBUS_STATUS_OK)
ubus_send_reply(c->context, &c->request, c->blob.head);
ubus_complete_deferred_request(c->context, &c->request, rv);
......@@ -175,6 +175,12 @@ rpc_exec_process_cb(struct uloop_process *p, int stat)
ustream_poll(&c->opipe.stream);
ustream_poll(&c->epipe.stream);
close(c->opipe.fd.fd);
close(c->epipe.fd.fd);
ustream_poll(&c->opipe.stream);
ustream_poll(&c->epipe.stream);
}
static void
......@@ -307,7 +313,7 @@ rpc_exec(const char **args, rpc_exec_write_cb_t in,
switch ((pid = fork()))
{
case -1:
return rpc_errno_status();
goto fail_fork;
case 0:
uloop_done();
......@@ -344,7 +350,7 @@ rpc_exec(const char **args, rpc_exec_write_cb_t in,
uloop_process_add(&c->process);
c->timeout.cb = rpc_exec_timeout_cb;
uloop_timeout_set(&c->timeout, RPC_EXEC_MAX_RUNTIME);
uloop_timeout_set(&c->timeout, rpc_exec_timeout);
if (c->stdin_cb)
{
......@@ -366,6 +372,10 @@ rpc_exec(const char **args, rpc_exec_write_cb_t in,
return UBUS_STATUS_OK;
fail_fork:
close(epipe[0]);
close(epipe[1]);
fail_epipe:
close(opipe[0]);
close(opipe[1]);
......@@ -375,5 +385,6 @@ fail_opipe:
close(ipipe[1]);
fail_ipipe:
free(c);
return rpc_errno_status();
}
This diff is collapsed.
#!/bin/bash
echo "preparation script"
pwd
adduser --disabled-password --gecos "" user
echo "user:user"|chpasswd
adduser --disabled-password --gecos "" admin
echo "admin:admin"|chpasswd
mkdir build
pushd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DIWINFO_SUPPORT=OFF
make
make install
popd
cp -r ./test/cmocka/files/* /
mkdir -p /var/run/rpcd
#!/bin/bash
echo "preparation script"
pwd
make unit-test -C ./build
#report part
#GitLab-CI output
make unit-coverage -C ./build
tar -zcvf unit-coverage.tar.gz ./build/unit-coverage
......@@ -24,7 +24,7 @@
#include <libubox/ustream.h>
#define RPC_EXEC_MAX_SIZE (4096 * 64)
#define RPC_EXEC_MAX_RUNTIME (30 * 1000)
#define RPC_EXEC_DEFAULT_TIMEOUT (120 * 1000)
#define ustream_for_each_read_buffer(stream, ptr, len) \
for (ptr = ustream_get_read_buf(stream, &len); \
......@@ -50,6 +50,7 @@
ustream_fd_init(&us, fd); \
} while(0)
extern int rpc_exec_timeout;
typedef int (*rpc_exec_write_cb_t)(struct ustream *, void *);
typedef int (*rpc_exec_read_cb_t)(struct blob_buf *, char *, int, void *);
......
/*
* RPC login guard mechanism
*
* Copyright (C) 2019 IOPSYS
*
* Author: Matija Amidzic <matija.amidzic@sartura.hr>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef __RPC_LOGIN_GUARD_H
#define __RPC_LOGIN_GUARD_H
#include <stdbool.h>
void login_guard_init();
bool login_guard_is_locked(const char *username);
void login_guard_add_attempt(const char *username);
void login_guard_remove_attempt(const char *username);
#endif //!__RPC_LOGIN_GUARD_H
......@@ -54,6 +54,7 @@ struct rpc_daemon_ops {
rpc_exec_read_cb_t err, rpc_exec_done_cb_t end,
void *priv, struct ubus_context *ctx,
struct ubus_request_data *req);
int *exec_timeout;
};
struct rpc_plugin {
......
......@@ -30,12 +30,16 @@
#include <libubox/avl.h>
#include <libubox/blobmsg_json.h>
#include <rpcd/uci.h>
#define RPC_SID_LEN 32
#define RPC_DEFAULT_SESSION_TIMEOUT 300
#define RPC_DEFAULT_SESSION_ID "00000000000000000000000000000000"
#define RPC_SESSION_DIRECTORY "/var/run/rpcd/sessions"
#define RPC_SESSION_ACL_DIR "/usr/share/rpcd/acl.d"
extern char apply_sid[RPC_SID_LEN + 1];
struct rpc_session {
struct avl_node avl;
char id[RPC_SID_LEN + 1];
......@@ -43,6 +47,7 @@ struct rpc_session {
struct uloop_timeout t;
struct avl_tree data;
struct avl_tree acls;
struct avl_tree granular_configs;
int timeout;
};
......@@ -81,4 +86,19 @@ void rpc_session_destroy_cb(struct rpc_session_cb *cb);
void rpc_session_freeze(void);
void rpc_session_thaw(void);
const char *rpc_session_get_username(struct blob_attr *sid);
struct rpc_session *rpc_session_get(const char *id);
struct rpc_session *rpc_session_create(int timeout);
void rpc_login_setup_acls(struct rpc_session *ses, struct uci_section *login);
int rpc_handle_login(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
void rpc_login_setup_acls(struct rpc_session *ses, struct uci_section *login);
struct uci_section *rpc_login_test_login(struct uci_context *uci,
const char *username, const char *password, const char *listen_source);
bool rpc_user_login_enabled(const char *username);
void rpc_session_destroy(struct rpc_session *ses);
void rpc_init_default_session(void);
#endif
......@@ -41,4 +41,31 @@ int rpc_uci_api_init(struct ubus_context *ctx);
void rpc_uci_purge_savedirs(void);
struct uci_granular_ctx_s *rpc_uci_load_granular(const char *username,
const char *config_name, const char *section_name,
const char *section_type, const char *sid);
int rpc_uci_check_granular_is_allowed(const char *option);
struct blob_buf *rpc_uci_get_glob_bb(void);
int rpc_uci_dump_package(struct uci_package *p, const char *name,
struct blob_attr *type, struct blob_attr *matches, const char *username,
const char *sid);
int rpc_uci_dump_section(struct uci_section *s, const char *name, int index, const char *username);
int rpc_uci_dump_option(struct uci_option *o, const char *name);
int rpc_uci_init_uci(void);
void rpc_uci_commit_cursor(struct uci_package *p);
int rpc_uci_set(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
int rpc_uci_revert_commit(struct ubus_context *ctx, struct blob_attr *msg,
bool commit);
int rpc_uci_add(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
int rpc_uci_delete(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
int rpc_uci_rename(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
void rpc_uci_clean(void);
#endif
/*
* granular uci access control
*
* Copyright (C) 2019 IOPSYS
*
* Author: Jakob Olsson <jakob.olsson@iopsys.eu>
* Author: Matija Amidzic <matija.amidzic@sartura.hr>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <libubox/avl.h>
#include <libubox/blobmsg_json.h>
#define GRANULAR_MAX_MATCH_OPTIONS 8 // maximum number of options by which to match the section (with uci lookup)
#define GRANULAR_MAX_ALLOWED_OPTIONS 16 // maximum size of list of allowed options
#define GRANULAR_MAX_MATCH_DEFINITIONS 8 // maximum number of match definitions per config
#define GRANULAR_MAX_STRING_LENGTH 64
#define GRANULAR_MAX_MATCHED_DEFINITIONS 32 // maximum number of matched definitions to parse when giving access
struct uci_granular_query_s {
const char *config_name;
const char *username;
const char *section_name;
const char *section_type;
};
/* high level of access has higher index */
enum option_access {
RPC_OA_NONE,
RPC_OA_OPTION,
RPC_OA_SECTION,
RPC_OA_FULL,
};
enum config_access {
RPC_CA_ALLOWED, // No restrictions at all in place (no uci_granular at all)
RPC_CA_RESTRICTED, // Restrictions of some sort are in place
};
struct match_option_s {
char name[GRANULAR_MAX_STRING_LENGTH];
char value[GRANULAR_MAX_STRING_LENGTH];
};
struct match_definition_s {
char match_section_type[GRANULAR_MAX_STRING_LENGTH];
char match_section_name[GRANULAR_MAX_STRING_LENGTH];
struct match_option_s match_options[GRANULAR_MAX_MATCH_OPTIONS];
int match_options_count;
char **allowed_options;
int allowed_options_count;
};
struct uci_granular_ctx_s {
struct match_definition_s *match_definitions[GRANULAR_MAX_MATCHED_DEFINITIONS];
/* Restriction placed on the sought after config file
* -1: Free access, no match/option pair found for configuration file
* 0: No access (restriction, but no options listed)
* 0<: Access restricted by match/option pair
*/
int match_definitions_count;
enum config_access state;
};
int rpc_uci_granular_init();
int rpc_uci_granular_setup(struct rpc_session *ses, struct blob_attr *acl_top, const char *username);
void rpc_uci_granular_get_allowed(struct rpc_session *ses, struct uci_granular_query_s *query, struct uci_granular_ctx_s *granular_ctx);
void uci_granular_exit(struct rpc_session *ses);
int uci_granular_parse_match_definition(struct blob_attr *acl_match_definition, struct match_definition_s *match_definition);
int rpc_uci_granular_destroy(void);
# IOPSYS RPCD Patches
## Login Guards
Login guards are a tool for brute force prevention. It will lock out a user for
a specified time, after a certain amount of failed login attempts.
The feature depends on a section ```brute-force```, provided in
```/etc/config/rpcd```. In the following example section 5 incorrect login
attempts are allowed before locking a user out, if no login attempts are seen
for 10 seconds, the attempt counter is reset. If 5 consecutive incorrect
attempts are reached the user will be locked out for 15 seconds.
```
config brute-force
option max_incorrect_attempts '5' # (seconds) maximum number of incorrect login attempts before lockout
option max_time_before_locking '10' # (seconds) maximum time period where number of incorrect login attempts are required before locking the account
option account_lock_duration '15' # (seconds) lockout duration after reaching maximum number of incorrect attempts.
```
These options do not have any default value set, meaning if any option is
missing, or given with incorrect types (i.e. characters), the login guard will
not be used.
### Login Guard example
```
root@iopsys:~# cat /etc/config/rpcd
config brute-force
option max_incorrect_attempts '2'
option max_time_before_locking '2'
option account_lock_duration '2'
...
root@iopsys:~# cat test.sh
ubus call session login '{"username":"user", "password":"incorrect"}'
ubus call session login '{"username":"user", "password":"incorrect"}'
ubus call session login '{"username":"user", "password":"user"}' #should fail
echo sleeping
sleep 2
ubus call session login '{"username":"user", "password":"user"}' #should succeed
root@iopsys:~#
root@iopsys:~#
root@iopsys:~#
root@iopsys:~# sh test.sh
Command failed: Permission denied
Command failed: Permission denied
Command failed: Permission denied
sleeping
{
"ubus_rpc_session": "76e61637adeef121fe4ecc4bebcf6458",
...
}
```
## Granular UCI Access Control
The granular uci access control offers increased control over what users are
able to see, add or change through RPCD's ```uci``` object, published on ubus.
The UCI granular control is implemented ontop of the existing RPCD access
control list functionality (```/usr/share/rpcd/acl.d/```). Alongside the keys
```uci``` and ```ubus```, at the ```read``` or ```write``` level in the json,
support for ```uci_granular``` has been added.
```
{
"user": {
"description": "User access",
"read": {
"uci": [
"samba"
"wireless",
]
},
"write": {
"ubus": {
...
},
"uci": [
"wireless",
],
"uci_granular": {
...
}
}
}
}
```
In the ```uci_granular``` object, configuration file names are used as keys to
arrays, holding permission objects for the corresponding configuration file.
```
"uci_granular": {
"wireless": [
...
],
"network": [
...
]
}
```
A permission object should contain a ```match``` object, specifying what
options, section types or section names (or any combination thereof) it should
match on. Secondly, a permission object can contain an ```option``` array,
specifying which options that should be made accessible. If an option array is
not specified (or contains ```*```), unrestricted access is granted to
matching sections, meaning the user may get, set, add, delete etc. freely
(assuming the access control list is provided as a ```write``` list). If options
are listed, the user will only be able to get or set those options, and have no
other section access.
```
"wireless": [
{
"match": {
".type": "wifi-iface",
"network": "lan"
},
"option": [
"ssid",
"key"
],
"match" : {
".type" : "wifi-device"
},
"option" : [
"*"
]
}
},
```
It is possible for a user to belong to several access groups with unique access
restrictions, in which case it will use the union of the restrictions.
### RPCD example
```
root@iopsys:~# cat /usr/share/rpcd/user.json
{
"user": {
"description": "End user access role",
"write": {
"ubus": {
"router.wps": [
"pbc",
"status",
"stop"
],
"router.wireless": [
"scan",
"scanresults",
"status",
"stas"
]
},
"uci": [
"samba",
"wireless",
"network"
],
"owsd": [
"wps",
"wifi.wps",
"wifi-repeater-success"
],
"uci_granular":{
"wireless" : [
{
"match" : {
".type" : "wifi-iface",
"network" : "lan"
},
"option" : [
"ssid",
"key"
]
},
{
"match" : {
".type" : "wifi-device"
},
"option" : [
"channel"
]
}
],
"network" : [
{
"match" : {
".name" : "lan",
},
"option" : [
"*"
]
},
{
"match" : {
".type" : "device"
}
}
]
}
}
}
}
root@iopsys:~# cat /etc/config/rpcd
config login
option username 'user'
option password '$p$user'
list write 'user'
root@iopsys:~# ubus call session login '{"username":"user", "password":"user"}'
| grep rpc
"ubus_rpc_session": "5270d2bdb1f89805bcb0a8cc7da9f964",
root@iopsys:~# ubus call uci get '{"config":"wireless", "ubus_rpc_session":
"5270d2bdb1f89805bcb0a8cc7da9f964"}'
{
"values": {
"wl0": {
".anonymous": false,
".type": "wifi-device",
".name": "wl0",
".index": 3,
"channel": "36"
},
"cfg063579": {
".anonymous": true,
".type": "wifi-iface",
".name": "cfg063579",
".index": 4,
"ssid": "iopsys-4C3A",
"key": "55RKNXNCCCCGSC"
},
"wl1": {
".anonymous": false,
".type": "wifi-device",
".name": "wl1",
".index": 5,
"channel": "36"
},
"cfg093579": {
".anonymous": true,
".type": "wifi-iface",
".name": "cfg093579",
".index": 6,
"key": "55RKNXNCCCCGSC",
"ssid": "test"
}
}
}
```
## Login Control
An option, ```option enabled```, has been added to ```/etc/config/rpcd``` under
```config login``` sections, making it possible to prevent a user from
logging in via rpcd (defaulting to ```enabled '1'``` if option is unspecified).
### Login Control Example
```
root@iopsys:~# cat /etc/config/rpcd
config login
option username 'user'
option password '$p$user'
option enabled '0'
list write 'user'
root@iopsys:~# ubus call session login '{"username":"user", "password":"user"}'
Command failed: Permission denied
root@iopsys:~# uci set rpcd.@login[-1].enabled=1
root@iopsys:~# uci commit
root@iopsys:~# /etc/init.d/rpcd restart
root@iopsys:~# ubus call session login '{"username":"user", "password":"user"}'
{
"ubus_rpc_session": "a9abd1bb1aa78f71f1387438b36d8acd",
...
}
root@iopsys:~# uci delete rpcd.@login[-1].enabled
root@iopsys:~# uci commit
root@iopsys:~# /etc/init.d/rpcd restart
root@iopsys:~# ubus call session login '{"username":"user", "password":"user"}'
{
"ubus_rpc_session": "8df19cc2e6c9dc682e91344e95942295",
...
}
```
......@@ -131,7 +131,7 @@ rpc_iwinfo_call_hardware_id(const char *name)
static void
rpc_iwinfo_add_encryption(const char *name, struct iwinfo_crypto_entry *e)
{
int ciph;
int ciph, wpa_version;
void *c, *d;
c = blobmsg_open_table(&buf, name);
......@@ -156,15 +156,9 @@ rpc_iwinfo_add_encryption(const char *name, struct iwinfo_crypto_entry *e)
{
d = blobmsg_open_array(&buf, "wpa");
if (e->wpa_version > 2)
{
blobmsg_add_u32(&buf, NULL, 1);
blobmsg_add_u32(&buf, NULL, 2);
}
else
{
blobmsg_add_u32(&buf, NULL, e->wpa_version);
}
for (wpa_version = 1; wpa_version <= 3; wpa_version++)
if (e->wpa_version & (1 << (wpa_version - 1)))
blobmsg_add_u32(&buf, NULL, wpa_version);
blobmsg_close_array(&buf, d);
......@@ -177,6 +171,12 @@ rpc_iwinfo_add_encryption(const char *name, struct iwinfo_crypto_entry *e)
if (e->auth_suites & IWINFO_KMGMT_8021x)
blobmsg_add_string(&buf, NULL, "802.1x");
if (e->auth_suites & IWINFO_KMGMT_SAE)
blobmsg_add_string(&buf, NULL, "sae");
if (e->auth_suites & IWINFO_KMGMT_OWE)
blobmsg_add_string(&buf, NULL, "owe");
if (!e->auth_suites ||
(e->auth_suites & IWINFO_KMGMT_NONE))
blobmsg_add_string(&buf, NULL, "none");
......@@ -290,6 +290,48 @@ rpc_iwinfo_call_hwmodes(const char *name)
}
}
static void rpc_iwinfo_call_hw_ht_mode()
{
const char *hwmode_str;
const char *htmode_str;
int32_t htmode = 0;
if (iw->htmode(ifname, &htmode))
return;
switch (htmode) {
case IWINFO_HTMODE_HT20:
htmode_str = "HT20";
hwmode_str = "n";
break;
case IWINFO_HTMODE_HT40:
htmode_str = "HT40";
hwmode_str = "n";
break;
case IWINFO_HTMODE_VHT80:
htmode_str = "VHT80";
hwmode_str = "ac";
break;
case IWINFO_HTMODE_VHT80_80:
htmode_str = "VHT80+80";
hwmode_str = "ac";
break;
case IWINFO_HTMODE_VHT160:
htmode_str = "VHT160";
hwmode_str = "ac";
break;
case IWINFO_HTMODE_NOHT:
htmode_str = "20";
hwmode_str = "a/g";
break;
default:
htmode_str = hwmode_str = "unknown";
break;
}
blobmsg_add_string(&buf, "hwmode", hwmode_str);
blobmsg_add_string(&buf, "htmode", htmode_str);
}
static void
rpc_iwinfo_call_str(const char *name, int (*func)(const char *, char *))
{
......@@ -341,6 +383,8 @@ rpc_iwinfo_info(struct ubus_context *ctx, struct ubus_object *obj,
rpc_iwinfo_call_htmodes("htmodes");
rpc_iwinfo_call_hwmodes("hwmodes");
rpc_iwinfo_call_hw_ht_mode();
c = blobmsg_open_table(&buf, "hardware");
rpc_iwinfo_call_hardware_id("id");
rpc_iwinfo_call_str("name", iw->hardware_name);
......@@ -412,6 +456,26 @@ rpc_iwinfo_scan(struct ubus_context *ctx, struct ubus_object *obj,
return UBUS_STATUS_OK;
}
static void
rpc_iwinfo_add_rateinfo(struct iwinfo_rate_entry *r)
{
blobmsg_add_u8(&buf, "ht", r->is_ht);
blobmsg_add_u8(&buf, "vht", r->is_vht);
blobmsg_add_u32(&buf, "mhz", r->mhz);
blobmsg_add_u32(&buf, "rate", r->rate);
if (r->is_ht) {
blobmsg_add_u32(&buf, "mcs", r->mcs);
blobmsg_add_u8(&buf, "40mhz", r->is_40mhz);
blobmsg_add_u8(&buf, "short_gi", r->is_short_gi);
}
else if (r->is_vht) {
blobmsg_add_u32(&buf, "mcs", r->mcs);
blobmsg_add_u32(&buf, "nss", r->nss);
blobmsg_add_u8(&buf, "short_gi", r->is_short_gi);
}
}
static int
rpc_iwinfo_assoclist(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
......@@ -458,21 +522,38 @@ rpc_iwinfo_assoclist(struct ubus_context *ctx, struct ubus_object *obj,
blobmsg_add_string(&buf, "mac", mac);
blobmsg_add_u32(&buf, "signal", a->signal);
blobmsg_add_u32(&buf, "signal_avg", a->signal_avg);
blobmsg_add_u32(&buf, "noise", a->noise);
blobmsg_add_u32(&buf, "inactive", a->inactive);
blobmsg_add_u32(&buf, "connected_time", a->connected_time);
blobmsg_add_u32(&buf, "thr", a->thr);
blobmsg_add_u8(&buf, "authorized", a->is_authorized);
blobmsg_add_u8(&buf, "authenticated", a->is_authenticated);
blobmsg_add_string(&buf, "preamble", a->is_preamble_short ? "short" : "long");
blobmsg_add_u8(&buf, "wme", a->is_wme);
blobmsg_add_u8(&buf, "mfp", a->is_mfp);
blobmsg_add_u8(&buf, "tdls", a->is_tdls);
blobmsg_add_u16(&buf, "mesh llid", a->llid);
blobmsg_add_u16(&buf, "mesh plid", a->plid);
blobmsg_add_string(&buf, "mesh plink", a->plink_state);
blobmsg_add_string(&buf, "mesh local PS", a->local_ps);
blobmsg_add_string(&buf, "mesh peer PS", a->peer_ps);
blobmsg_add_string(&buf, "mesh non-peer PS", a->nonpeer_ps);
e = blobmsg_open_table(&buf, "rx");
blobmsg_add_u32(&buf, "rate", a->rx_rate.rate);
blobmsg_add_u32(&buf, "mcs", a->rx_rate.mcs);
blobmsg_add_u8(&buf, "40mhz", a->rx_rate.is_40mhz);
blobmsg_add_u8(&buf, "short_gi", a->rx_rate.is_short_gi);
blobmsg_add_u64(&buf, "drop_misc", a->rx_drop_misc);
blobmsg_add_u32(&buf, "packets", a->rx_packets);
blobmsg_add_u32(&buf, "bytes", a->rx_bytes);
rpc_iwinfo_add_rateinfo(&a->rx_rate);
blobmsg_close_table(&buf, e);
e = blobmsg_open_table(&buf, "tx");
blobmsg_add_u32(&buf, "rate", a->tx_rate.rate);
blobmsg_add_u32(&buf, "mcs", a->tx_rate.mcs);
blobmsg_add_u8(&buf, "40mhz", a->tx_rate.is_40mhz);
blobmsg_add_u8(&buf, "short_gi", a->tx_rate.is_short_gi);
blobmsg_add_u32(&buf, "failed", a->tx_failed);
blobmsg_add_u32(&buf, "retries", a->tx_retries);
blobmsg_add_u32(&buf, "packets", a->tx_packets);
blobmsg_add_u32(&buf, "bytes", a->tx_bytes);
rpc_iwinfo_add_rateinfo(&a->tx_rate);
blobmsg_close_table(&buf, e);
found = true;
......@@ -495,6 +576,45 @@ rpc_iwinfo_assoclist(struct ubus_context *ctx, struct ubus_object *obj,
return UBUS_STATUS_OK;
}
static int
rpc_iwinfo_survey(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
char res[IWINFO_BUFSIZE];
struct iwinfo_survey_entry *e;
void *c, *d;
int i, rv, len;
blob_buf_init(&buf, 0);
rv = rpc_iwinfo_open(msg);
c = blobmsg_open_array(&buf, "results");
if (rv || iw->survey(ifname, res, &len) || len < 0)
return UBUS_STATUS_OK;
for (i = 0; i < len; i += sizeof(struct iwinfo_survey_entry)) {
e = (struct iwinfo_survey_entry *)&res[i];
d = blobmsg_open_table(&buf, NULL);
blobmsg_add_u32(&buf, "mhz", e->mhz);
blobmsg_add_u32(&buf, "noise", e->noise);
blobmsg_add_u64(&buf, "active_time", e->active_time);
blobmsg_add_u64(&buf, "busy_time", e->busy_time);
blobmsg_add_u64(&buf, "busy_time_ext", e->busy_time_ext);
blobmsg_add_u64(&buf, "rx_time", e->rxtime);
blobmsg_add_u64(&buf, "tx_time", e->txtime);
blobmsg_close_table(&buf, d);
}
blobmsg_close_array(&buf, c);
ubus_send_reply(ctx, req, buf.head);
rpc_iwinfo_close();
return UBUS_STATUS_OK;
}
static int
rpc_iwinfo_freqlist(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
......@@ -775,6 +895,7 @@ rpc_iwinfo_api_init(const struct rpc_daemon_ops *o, struct ubus_context *ctx)
UBUS_METHOD("freqlist", rpc_iwinfo_freqlist, rpc_device_policy),
UBUS_METHOD("txpowerlist", rpc_iwinfo_txpowerlist, rpc_device_policy),
UBUS_METHOD("countrylist", rpc_iwinfo_countrylist, rpc_device_policy),
UBUS_METHOD("survey", rpc_iwinfo_survey, rpc_device_policy),
UBUS_METHOD("phyname", rpc_iwinfo_phyname, rpc_uci_policy),
};
......
/*
* RPC login guard mechanism
*
* Copyright (C) 2019 IOPSYS
*
* Author: Matija Amidzic <matija.amidzic@sartura.hr>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <syslog.h>
#include <libubox/avl.h>
#include <libubox/avl-cmp.h>
#include <libubox/uloop.h>
#include <uci.h>
#include <string.h>
#include <stdlib.h>
#include <rpcd/login_guard.h>
#define CONFIG_GUARD "brute-force"
#define CONFIG_MAX_ATTEMPTS "max_incorrect_attempts"
#define CONFIG_MAX_TIME_BEFORE_LOCK "max_time_before_locking"
#define CONFIG_LOCK_DURATION "account_lock_duration"
struct login_guard_s {
struct avl_tree login_attempts;
int max_attempts;
int max_time_before_lock;
int lock_duration;
bool enabled;
};
struct login_attempt_ctx {
struct avl_node avl;
struct uloop_timeout lock_timer;
struct uloop_timeout unlock_timer;
bool is_locked;
int incorrect_attempts;
};
static struct login_attempt_ctx *login_guard_find_attempt(const char *username);
static void login_guard_lock_timeout(struct uloop_timeout *t);
static void login_guard_unlock_timeout(struct uloop_timeout *t);
static struct login_attempt_ctx *login_guard_create_attempt(const char *username);
static void login_guard_delete_attempt(struct login_attempt_ctx *login_attempt);
static struct login_guard_s login_guard = {0};
static struct login_attempt_ctx *login_guard_find_attempt(const char *username)
{
struct login_attempt_ctx *attempt = NULL;
struct login_attempt_ctx *temp = NULL;
avl_for_each_element(&login_guard.login_attempts, temp, avl) {
if (strcmp(temp->avl.key, username) == 0) {
attempt = temp;
// we found the attempt, break
break;
}
}
return attempt;
}
static void login_guard_lock_timeout(struct uloop_timeout *t)
{
struct login_attempt_ctx *login_attempt;
login_attempt = container_of(t, struct login_attempt_ctx, lock_timer);
login_attempt->incorrect_attempts = 0;
}
static void login_guard_unlock_timeout(struct uloop_timeout *t)
{
struct login_attempt_ctx *login_attempt;
login_attempt = container_of(t, struct login_attempt_ctx, unlock_timer);
login_guard_delete_attempt(login_attempt);
}
static struct login_attempt_ctx *login_guard_create_attempt(const char *username)
{
struct login_attempt_ctx *attempt = calloc(1, sizeof(*attempt));
if (attempt == NULL) {
return NULL;
}
attempt->lock_timer.cb = login_guard_lock_timeout;
attempt->unlock_timer.cb = login_guard_unlock_timeout;
attempt->avl.key = strdup(username);
if (attempt->avl.key == NULL) {
free(attempt);
return NULL;
}
avl_insert(&login_guard.login_attempts, &attempt->avl);
return attempt;
}
static void login_guard_delete_attempt(struct login_attempt_ctx *attempt)
{
avl_delete(&login_guard.login_attempts, &attempt->avl);
uloop_timeout_cancel(&attempt->lock_timer);
uloop_timeout_cancel(&attempt->unlock_timer);
free((void *) attempt->avl.key);
free(attempt);
}
bool login_guard_is_locked(const char *username)
{
if (!login_guard.enabled)
return false;
struct login_attempt_ctx *attempt = login_guard_find_attempt(username);
if (attempt != NULL) {
if (attempt->is_locked) {
int remaining_time = uloop_timeout_remaining(&attempt->unlock_timer);
syslog(LOG_NOTICE, "user account '%s' locked for %d seconds", username, remaining_time / 1000);
return true;
} else {
return false;
}
}