...
 
Commits (50)
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)
......@@ -38,7 +40,17 @@ 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)
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 "")
......@@ -65,6 +77,29 @@ IF (IWINFO_SUPPORT)
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
......@@ -280,6 +280,42 @@ __rpc_check_path(const struct blobmsg_policy *policy, size_t policy_len,
RPC_F_ ## policy_selector ## _PATH, \
RPC_F_ ## policy_selector ## _SESSION, \
perm, msg, path, s)
/* restrict that pathptr path starts with prefix
* for example: /tmp/dummy.log starts with /tmp
* note: this does not work when pathptr is a dead softlink from outside
* the restricted path to (under the restricted path).
*/
static int
rpc_restrict_path(char *pathptr, char *prefix)
{
size_t pathlen = strlen(pathptr);
char path[PATH_MAX] = {0}, resolved_path[PATH_MAX] = {0}, *slash;
if (!pathptr || pathlen <= 0 || pathlen >= PATH_MAX)
return UBUS_STATUS_INVALID_ARGUMENT;
strncpy(path, pathptr, pathlen);
/* get realpath of /path/to/file */
if (realpath(path, resolved_path) == NULL) {
slash = strrchr(path, '/');
if (!slash)
return UBUS_STATUS_INVALID_ARGUMENT;
memset(slash, 0, pathlen - (size_t)(slash - path));
/* get realpath of /path/to */
if (realpath(path, resolved_path) == NULL)
return UBUS_STATUS_INVALID_ARGUMENT;
}
/* reject paths that do not start with prefix, e.g. /tmp */
if (strncmp(resolved_path, prefix, strlen(prefix)) != 0)
return UBUS_STATUS_PERMISSION_DENIED;
/* reject paths like /tmpdir */
if (resolved_path[strlen(prefix)] != '/' &&
resolved_path[strlen(prefix)] != '\0')
return UBUS_STATUS_PERMISSION_DENIED;
return UBUS_STATUS_OK;
}
static int
rpc_file_read(struct ubus_context *ctx, struct ubus_object *obj,
......@@ -362,6 +398,26 @@ out:
return rv;
}
static int
rpc_file_read_tmp_juci(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__RPC_F_RB_MAX];
int rv;
blobmsg_parse(rpc_file_RB_policy, __RPC_F_RB_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[RPC_F_RB_PATH])
return UBUS_STATUS_INVALID_ARGUMENT;
rv = rpc_restrict_path(blobmsg_get_string(tb[RPC_F_RB_PATH]), "/tmp/juci");
if (rv)
return rv;
return rpc_file_read(ctx, obj, req, method, msg);
}
static int
rpc_file_write(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
......@@ -424,6 +480,27 @@ out:
return 0;
}
static int
rpc_file_write_tmp_juci(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__RPC_F_RW_MAX];
int rv;
blobmsg_parse(rpc_file_RW_policy, __RPC_F_RW_MAX, tb,
blob_data(msg), blob_len(msg));
if (!tb[RPC_F_RW_PATH])
return UBUS_STATUS_INVALID_ARGUMENT;
rv = rpc_restrict_path(blobmsg_get_string(tb[RPC_F_RW_PATH]), "/tmp/juci");
if (rv)
return rv;
return rpc_file_write(ctx, obj, req, method, msg);
}
static int
rpc_file_md5(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
......@@ -961,7 +1038,9 @@ rpc_file_api_init(const struct rpc_daemon_ops *o, struct ubus_context *ctx)
{
static const struct ubus_method file_methods[] = {
UBUS_METHOD("read", rpc_file_read, rpc_file_RB_policy),
UBUS_METHOD("read_tmp_juci", rpc_file_read_tmp_juci, rpc_file_RB_policy),
UBUS_METHOD("write", rpc_file_write, rpc_file_RW_policy),
UBUS_METHOD("write_tmp_juci", rpc_file_write_tmp_juci, rpc_file_RW_policy),
UBUS_METHOD("list", rpc_file_list, rpc_file_R_policy),
UBUS_METHOD("stat", rpc_file_stat, rpc_file_R_policy),
UBUS_METHOD("md5", rpc_file_md5, rpc_file_R_policy),
......
#!/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
/*
* 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
......@@ -30,6 +30,8 @@
#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"
......@@ -45,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;
};
......@@ -83,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",
...
}
```
/*
* 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;
}
}
return false;
}
void login_guard_add_attempt(const char *username) {
if (!login_guard.enabled)
return;
struct login_attempt_ctx *attempt = login_guard_find_attempt(username);
syslog(LOG_NOTICE, "failed login attempt for user: %s", username);
if (attempt == NULL) { // create new attempt
attempt = login_guard_create_attempt(username);
if (attempt == NULL) {
syslog(LOG_ERR, "memory allocation failed");
return;
}
attempt->incorrect_attempts++;
uloop_timeout_set(&attempt->lock_timer, login_guard.max_time_before_lock * 1000);
} else { // increment attempt
if (attempt->incorrect_attempts == 0) {
uloop_timeout_set(&attempt->lock_timer, login_guard.max_time_before_lock * 1000);
}
attempt->incorrect_attempts++;
if (attempt->incorrect_attempts >= login_guard.max_attempts) { // lock account
attempt->is_locked = true;
uloop_timeout_set(&attempt->unlock_timer, login_guard.lock_duration * 1000);
}
}
}
void login_guard_remove_attempt(const char *username)
{
if (!login_guard.enabled)
return;
syslog(LOG_NOTICE, "successful login for user: %s", username);
struct login_attempt_ctx *attempt = login_guard_find_attempt(username);
if (attempt != NULL) {
login_guard_delete_attempt(attempt);
}
}
void login_guard_init()
{
struct uci_context *uci = NULL;
struct uci_package *p = NULL;
struct uci_section *s;
struct uci_element *e;
struct uci_ptr ptr = { .package = "rpcd" };
uci = uci_alloc_context();
if (!uci) {
goto out;
}
uci_load(uci, ptr.package, &p);
if (!p)
goto out;
uci_foreach_element(&p->sections, e)
{
s = uci_to_section(e);
if (strcmp(s->type, CONFIG_GUARD))
continue;
ptr.section = s->e.name;
ptr.s = NULL;
/* max_incorrrect_attempts parse */
ptr.option = CONFIG_MAX_ATTEMPTS;
ptr.o = NULL;
if (uci_lookup_ptr(uci, &ptr, NULL, true))
continue;
if (ptr.o->type != UCI_TYPE_STRING)
continue;
int nr_attempts = atoi(ptr.o->v.string);
if (nr_attempts <= 0)
continue;
login_guard.max_attempts = nr_attempts;
/* max_time_before_locking parse */
ptr.option = CONFIG_MAX_TIME_BEFORE_LOCK;
ptr.o = NULL;
if (uci_lookup_ptr(uci, &ptr, NULL, true))
continue;
if (ptr.o->type != UCI_TYPE_STRING)
continue;
int max_time = atoi(ptr.o->v.string);
if (max_time <= 0)
continue;
login_guard.max_time_before_lock = max_time;
/* lock_duration parse */
ptr.option = CONFIG_LOCK_DURATION;
ptr.o = NULL;
if (uci_lookup_ptr(uci, &ptr, NULL, true))
continue;
if (ptr.o->type != UCI_TYPE_STRING)
continue;
int duration = atoi(ptr.o->v.string);
if (duration <= 0)
continue;
login_guard.lock_duration = duration;
// all options present, enable login guard
login_guard.enabled = true;
avl_init(&login_guard.login_attempts, avl_strcmp, false, NULL);
syslog(LOG_NOTICE, "login guard enabled");
break;
}
out:
if (uci)
uci_free_context(uci);
return;
}
......@@ -25,10 +25,11 @@
#include <signal.h>
#include <sys/stat.h>
#include <rpcd/session.h>
#include <rpcd/uci.h>
#include <rpcd/session.h>
#include <rpcd/plugin.h>
#include <rpcd/exec.h>
#include <rpcd/uci_granular.h>
static struct ubus_context *ctx;
static bool respawn = false;
......
......@@ -27,12 +27,15 @@
#include <glob.h>
#include <uci.h>
#include <limits.h>
#include <syslog.h>
#ifdef HAVE_SHADOW
#include <shadow.h>
#endif
#include <rpcd/session.h>
#include <rpcd/login_guard.h>
#include <rpcd/uci_granular.h>
static struct avl_tree sessions;
static struct blob_buf buf;
......@@ -120,11 +123,13 @@ enum {
RPC_L_USERNAME,
RPC_L_PASSWORD,
RPC_L_TIMEOUT,
RPC_L_OWSD_LISTENER,
__RPC_L_MAX,
};
static const struct blobmsg_policy login_policy[__RPC_L_MAX] = {
[RPC_L_USERNAME] = { .name = "username", .type = BLOBMSG_TYPE_STRING },
[RPC_L_PASSWORD] = { .name = "password", .type = BLOBMSG_TYPE_STRING },
[RPC_L_OWSD_LISTENER] = { .name = "_owsd_listen", .type = BLOBMSG_TYPE_STRING },
[RPC_L_TIMEOUT] = { .name = "timeout", .type = BLOBMSG_TYPE_INT32 },
};
......@@ -255,7 +260,7 @@ rpc_touch_session(struct rpc_session *ses)
uloop_timeout_set(&ses->t, ses->timeout * 1000);
}
static void
void
rpc_session_destroy(struct rpc_session *ses)
{
struct rpc_session_acl *acl, *nacl;
......@@ -279,6 +284,8 @@ rpc_session_destroy(struct rpc_session *ses)
avl_remove_all_elements(&ses->data, data, avl, ndata)
free(data);
uci_granular_exit(ses);
avl_delete(&sessions, &ses->avl);
free(ses);
}
......@@ -311,7 +318,7 @@ rpc_session_new(void)
return ses;
}
static struct rpc_session *
struct rpc_session *
rpc_session_create(int timeout)
{
struct rpc_session *ses;
......@@ -337,7 +344,7 @@ rpc_session_create(int timeout)
return ses;
}
static struct rpc_session *
struct rpc_session *
rpc_session_get(const char *id)
{
struct rpc_session *ses;
......@@ -825,9 +832,9 @@ rpc_login_test_password(const char *hash, const char *password)
return (crypt_hash && !strcmp(crypt_hash, hash));
}
static struct uci_section *
struct uci_section *
rpc_login_test_login(struct uci_context *uci,
const char *username, const char *password)
const char *username, const char *password, const char *listen_source)
{
struct uci_package *p = NULL;
struct uci_section *s;
......@@ -862,11 +869,34 @@ rpc_login_test_login(struct uci_context *uci,
if (strcmp(ptr.o->v.string, username))
continue;