mirror of
https://github.com/falk-werner/webfuse-provider
synced 2024-10-27 20:44:10 +00:00
Merge pull request #42 from falk-werner/remove-example
feature: removed example To focus on webfuse library, daemon, provider and web based example are moved into separate repositories: daemon: https://github.com/falk-werner/webfused provider: https://github.com/falk-werner/webfuse-provider example: https://github.com/falk-werner/webfuse-example
This commit is contained in:
commit
7023fcd14a
@ -2,7 +2,6 @@ cmake_minimum_required (VERSION 3.10)
|
||||
project(webfuse VERSION 0.2.0 DESCRIPTION "Websocket filesystem based on libfuse")
|
||||
|
||||
option(WITHOUT_TESTS "disable unit tests" OFF)
|
||||
option(WITHOUT_EXAMPLE "disable example" OFF)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
include(coverage)
|
||||
@ -190,81 +189,6 @@ install(FILES include/webfuse_provider.h DESTINATION include)
|
||||
install(DIRECTORY include/webfuse/provider DESTINATION include/webfuse)
|
||||
install(FILES "${PROJECT_BINARY_DIR}/libwebfuse-provider.pc" DESTINATION lib${LIB_SUFFIX}/pkgconfig)
|
||||
|
||||
|
||||
# examples
|
||||
|
||||
pkg_check_modules(OPENSSL REQUIRED openssl)
|
||||
|
||||
if(NOT WITHOUT_EXAMPLE)
|
||||
|
||||
# libuserdb
|
||||
|
||||
add_library(userdb STATIC
|
||||
example/lib/userdb/src/userdb.c
|
||||
)
|
||||
|
||||
target_include_directories(userdb PUBLIC
|
||||
example/lib/userdb/include
|
||||
${OPENSSL_INCLUDE_DIRS}
|
||||
${JANSSON_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_compile_options(userdb PUBLIC ${OPENSSL_CFLAGS_OTHER})
|
||||
|
||||
# daemon
|
||||
|
||||
add_executable(webfused
|
||||
example/daemon/main.c
|
||||
)
|
||||
|
||||
target_link_libraries(webfused PUBLIC webfuse-adapter userdb ${OPENSSL_LIBRARIES} ${EXTRA_LIBS})
|
||||
target_compile_options(webfused PUBLIC ${OPENSSL_CFLAGS_OTHER})
|
||||
|
||||
# provider
|
||||
|
||||
add_executable(webfuse-provider-app
|
||||
example/provider/main.c
|
||||
)
|
||||
|
||||
set_target_properties(webfuse-provider-app PROPERTIES OUTPUT_NAME webfuse-provider)
|
||||
|
||||
target_link_libraries(webfuse-provider-app PUBLIC webfuse-provider ${EXTRA_LIBS})
|
||||
target_include_directories(webfuse-provider-app PUBLIC ${EXTRA_INCLUDE_DIRS})
|
||||
|
||||
# static-filesystem-provider
|
||||
|
||||
add_executable(static-filesystem-provider
|
||||
example/provider/static_filesystem.c
|
||||
)
|
||||
|
||||
target_link_libraries(static-filesystem-provider PUBLIC webfuse-provider ${EXTRA_LIBS})
|
||||
target_include_directories(static-filesystem-provider PUBLIC ${EXTRA_INCLUDE_DIRS})
|
||||
target_compile_options(static-filesystem-provider PUBLIC ${EXTRA_CFLAGS})
|
||||
|
||||
# webfuse-passwd
|
||||
|
||||
add_executable(webfuse-passwd
|
||||
example/passwd/main.c
|
||||
)
|
||||
|
||||
target_link_libraries(webfuse-passwd PUBLIC
|
||||
userdb
|
||||
${OPENSSL_LIBRARIES}
|
||||
${JANSSON_LIBRARIES}
|
||||
)
|
||||
|
||||
target_include_directories(webfuse-passwd PUBLIC
|
||||
example/passwd
|
||||
example/lib/userdb/include
|
||||
${OPENSSL_INCLUDE_DIRS}
|
||||
${JANSSON_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_compile_options(webfuse-passwd PUBLIC ${OPENSSL_CFLAGS_OTHER})
|
||||
|
||||
|
||||
endif(NOT WITHOUT_EXAMPLE)
|
||||
|
||||
# tests
|
||||
|
||||
if(NOT WITHOUT_TESTS)
|
||||
|
19
README.md
19
README.md
@ -9,10 +9,11 @@ webfuse combines libwebsockets and libfuse. It allows ot attach a remote filesys
|
||||
## Contents
|
||||
|
||||
- [Motivation](#Motivation)
|
||||
- [Fellow Repositories](#Fellow-Repositories)
|
||||
- [Concept](#Concept)
|
||||
- [Similar Projects](#Similar-Projects)
|
||||
- [API](#API)
|
||||
- [Build and run](#Build-and-run)
|
||||
- [Build](#Build)
|
||||
- [Dependencies](#Dependencies)
|
||||
|
||||
## Motivation
|
||||
@ -32,6 +33,12 @@ To avoid Steps 1 and 2, it would be great to keep the update file entirely in we
|
||||
|
||||
webfuse solves this problem by using the [WebSocket](https://en.wikipedia.org/wiki/WebSocket) protocol. The emdedded device runs a service, known as webfuse adapter, awaiting incoming connections, e.g. from a web browser. The browser acts as a file system provider, providing the update file to the device.
|
||||
|
||||
## Fellow Repositories
|
||||
|
||||
- **[webfuse-example](https://github.com/falk-werner/webfuse-example)**: Example of webfuse
|
||||
- **[webfused](https://github.com/falk-werner/webfused)**: Reference implementation of webfuse daemon
|
||||
- **[webfuse-provider](https://github.com/falk-werner/webfuse-provider)**: Reference implementation of webfuse provider
|
||||
|
||||
## Concept
|
||||
|
||||
![concept](doc/concept.png)
|
||||
@ -377,7 +384,7 @@ The authenticator type **username** is used to authenticate via username and pas
|
||||
|
||||
**Note** that no further encryption is done, so this authenticator type should not be used over unencrypted websocket connections.
|
||||
|
||||
## Build and run
|
||||
## Build
|
||||
|
||||
To install dependencies, see below.
|
||||
|
||||
@ -385,19 +392,15 @@ To install dependencies, see below.
|
||||
mkdir .build
|
||||
cd .build
|
||||
cmake ..
|
||||
mkdir test
|
||||
./webfused -m test --document_root=../exmaple/daemon/www --port=4711
|
||||
make
|
||||
|
||||
### Build options
|
||||
|
||||
By default, unit tests and example application are enabled. You can disable them using the following cmake options:
|
||||
By default, unit tests are enabled. You can disable them using the following cmake options:
|
||||
|
||||
- **WITHOUT_TESTS**: disable tests
|
||||
`cmake -DWITHOUT_TESTS=ON ..`
|
||||
|
||||
- **WITHOUT_EXAMPLE**: disable example
|
||||
`cmake -DWITHOUD_EXAMPLE=ON ..`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [libfuse3](https://github.com/libfuse/libfuse/)
|
||||
|
@ -1,196 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include <webfuse_adapter.h>
|
||||
#include <userdb.h>
|
||||
|
||||
#define SERVICE_TIMEOUT (1 * 1000)
|
||||
|
||||
struct args
|
||||
{
|
||||
struct wf_server_config * config;
|
||||
char * passwd_path;
|
||||
bool show_help;
|
||||
};
|
||||
|
||||
static bool shutdown_requested = false;
|
||||
|
||||
static void show_help(void)
|
||||
{
|
||||
printf(
|
||||
"webfused, Copyright (c) 2019, webfuse authors <https://github.com/falk-werner/webfuse>\n"
|
||||
"Websocket file system daemon\n"
|
||||
"\n"
|
||||
"Usage: webfused [m <mount_point>] [-d <document_root] [-n <vhost_name>] [-p <port>]\n"
|
||||
" [-c <server_cert_path>] [-k <server_key_path>] [-P <passwd_path>]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
"\t-m, --mount_point Path of mount point (required)\n"
|
||||
"\t-d, --document_root Path of www directory (default: not set, www disabled)\n"
|
||||
"\t-c, --server_cert_path Path of servers own certificate (default: not set, TLS disabled)\n"
|
||||
"\t-k, --server_key_path Path of servers private key (default: not set, TLS disabled)\n"
|
||||
"\t-n, --vhost_name Name of virtual host (default: \"localhost\")\n"
|
||||
"\t-p, --port Number of servers port (default: 8080)\n"
|
||||
"\t-P, --passwd_path Path to password file (default: not set, authentication disabled)\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
static bool authenticate(struct wf_credentials * creds, void * user_data)
|
||||
{
|
||||
bool result = false;
|
||||
struct args * args = user_data;
|
||||
|
||||
char const * username = wf_credentials_get(creds, "username");
|
||||
char const * password = wf_credentials_get(creds, "password");
|
||||
if ((NULL != username) && (NULL != password))
|
||||
{
|
||||
struct userdb * db = userdb_create("");
|
||||
result = userdb_load(db, args->passwd_path);
|
||||
if (result)
|
||||
{
|
||||
result = userdb_check(db, username, password);
|
||||
}
|
||||
|
||||
userdb_dispose(db);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int parse_arguments(int argc, char * argv[], struct args * args)
|
||||
{
|
||||
static struct option const options[] =
|
||||
{
|
||||
{"mount_point", required_argument, NULL, 'm'},
|
||||
{"document_root", required_argument, NULL, 'd'},
|
||||
{"server_cert_path", required_argument, NULL, 'c'},
|
||||
{"server_key_path", required_argument, NULL, 'k'},
|
||||
{"vhost_name", required_argument, NULL, 'n'},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
{"passwd_path", required_argument, NULL, 'P'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
bool result = EXIT_SUCCESS;
|
||||
bool finished = false;
|
||||
bool has_mountpoint = false;
|
||||
while ((!finished) && (EXIT_SUCCESS == result))
|
||||
{
|
||||
int option_index = 0;
|
||||
int const c = getopt_long(argc, argv, "m:d:c:k:n:p:P:h", options, &option_index);
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case -1:
|
||||
finished = true;
|
||||
break;
|
||||
case 'h':
|
||||
args->show_help = true;
|
||||
finished = true;
|
||||
break;
|
||||
case 'm':
|
||||
wf_server_config_set_mountpoint(args->config, optarg);
|
||||
has_mountpoint = true;
|
||||
break;
|
||||
case 'd':
|
||||
wf_server_config_set_documentroot(args->config, optarg);
|
||||
break;
|
||||
case 'c':
|
||||
wf_server_config_set_certpath(args->config, optarg);
|
||||
break;
|
||||
case 'k':
|
||||
wf_server_config_set_keypath(args->config, optarg);
|
||||
break;
|
||||
case 'n':
|
||||
wf_server_config_set_vhostname(args->config, optarg);
|
||||
break;
|
||||
case 'p':
|
||||
wf_server_config_set_port(args->config, atoi(optarg));
|
||||
break;
|
||||
case 'P':
|
||||
free(args->passwd_path);
|
||||
args->passwd_path = strdup(optarg);
|
||||
wf_server_config_add_authenticator(args->config,
|
||||
"username",
|
||||
&authenticate,
|
||||
args);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "error: unknown argument\n");
|
||||
result = EXIT_FAILURE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((EXIT_SUCCESS == result) && (!args->show_help))
|
||||
{
|
||||
if (!has_mountpoint)
|
||||
{
|
||||
fprintf(stderr, "error: missing mount point\n");
|
||||
result = EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (EXIT_SUCCESS != result)
|
||||
{
|
||||
args->show_help = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void on_interrupt(int signal_id)
|
||||
{
|
||||
(void) signal_id;
|
||||
|
||||
shutdown_requested = true;
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
struct args args;
|
||||
args.config = wf_server_config_create();
|
||||
wf_server_config_set_vhostname(args.config, "localhost");
|
||||
wf_server_config_set_port(args.config, 8080);
|
||||
args.passwd_path = NULL;
|
||||
args.show_help = false;
|
||||
|
||||
int result = parse_arguments(argc, argv, &args);
|
||||
|
||||
if (!args.show_help)
|
||||
{
|
||||
signal(SIGINT, on_interrupt);
|
||||
struct wf_server * server = wf_server_create(args.config);
|
||||
if (NULL != server)
|
||||
{
|
||||
while (!shutdown_requested)
|
||||
{
|
||||
wf_server_service(server, SERVICE_TIMEOUT);
|
||||
}
|
||||
|
||||
wf_server_dispose(server);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "fatal: unable start server\n");
|
||||
result = EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
show_help();
|
||||
}
|
||||
|
||||
free(args.passwd_path);
|
||||
wf_server_config_dispose(args.config);
|
||||
return result;
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>WebFuse Example</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="style/main.css">
|
||||
<script type="module" src="js/startup.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page">
|
||||
<div class="window">
|
||||
<div class="title">Connection</div>
|
||||
<div id="connection"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,287 +0,0 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"accessor-pairs": "error",
|
||||
"array-bracket-newline": "error",
|
||||
"array-bracket-spacing": "error",
|
||||
"array-callback-return": "error",
|
||||
"array-element-newline": ["error", "consistent"],
|
||||
"arrow-body-style": "error",
|
||||
"arrow-parens": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"arrow-spacing": [
|
||||
"error",
|
||||
{
|
||||
"after": true,
|
||||
"before": true
|
||||
}
|
||||
],
|
||||
"block-scoped-var": "error",
|
||||
"block-spacing": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"brace-style": "off",
|
||||
"callback-return": "off",
|
||||
"camelcase": "error",
|
||||
"capitalized-comments": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"class-methods-use-this": "off",
|
||||
"comma-dangle": "error",
|
||||
"comma-spacing": [
|
||||
"error",
|
||||
{
|
||||
"after": true,
|
||||
"before": false
|
||||
}
|
||||
],
|
||||
"comma-style": [
|
||||
"error",
|
||||
"last"
|
||||
],
|
||||
"complexity": "error",
|
||||
"computed-property-spacing": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"consistent-return": "error",
|
||||
"consistent-this": "error",
|
||||
"curly": "error",
|
||||
"default-case": "error",
|
||||
"dot-location": "error",
|
||||
"dot-notation": "error",
|
||||
"eol-last": "off",
|
||||
"eqeqeq": "off",
|
||||
"func-call-spacing": "error",
|
||||
"func-name-matching": "error",
|
||||
"func-names": "error",
|
||||
"func-style": [
|
||||
"error",
|
||||
"declaration"
|
||||
],
|
||||
"function-paren-newline": "error",
|
||||
"generator-star-spacing": "error",
|
||||
"global-require": "error",
|
||||
"guard-for-in": "error",
|
||||
"handle-callback-err": "error",
|
||||
"id-blacklist": "error",
|
||||
"id-length": "error",
|
||||
"id-match": "error",
|
||||
"implicit-arrow-linebreak": [
|
||||
"error",
|
||||
"beside"
|
||||
],
|
||||
"indent": "off",
|
||||
"indent-legacy": "off",
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "error",
|
||||
"key-spacing": "off",
|
||||
"keyword-spacing": "off",
|
||||
"line-comment-position": "error",
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"lines-around-comment": "error",
|
||||
"lines-around-directive": "error",
|
||||
"lines-between-class-members": "off",
|
||||
"max-classes-per-file": "error",
|
||||
"max-depth": "error",
|
||||
"max-len": "off",
|
||||
"max-lines": "error",
|
||||
"max-lines-per-function": "off",
|
||||
"max-nested-callbacks": "error",
|
||||
"max-params": "off",
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": "off",
|
||||
"multiline-comment-style": "error",
|
||||
"new-cap": "error",
|
||||
"new-parens": "error",
|
||||
"newline-after-var": "off",
|
||||
"newline-before-return": "error",
|
||||
"newline-per-chained-call": "error",
|
||||
"no-alert": "error",
|
||||
"no-array-constructor": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-await-in-loop": "error",
|
||||
"no-bitwise": "off",
|
||||
"no-buffer-constructor": "error",
|
||||
"no-caller": "error",
|
||||
"no-catch-shadow": "error",
|
||||
"no-confusing-arrow": "error",
|
||||
"no-continue": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-else-return": "off",
|
||||
"no-empty-function": "off",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-extra-parens": "off",
|
||||
"no-floating-decimal": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-implicit-globals": "off",
|
||||
"no-implied-eval": "error",
|
||||
"no-inline-comments": "error",
|
||||
"no-invalid-this": "error",
|
||||
"no-iterator": "error",
|
||||
"no-label-var": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-magic-numbers": "off",
|
||||
"no-misleading-character-class": "error",
|
||||
"no-mixed-operators": "error",
|
||||
"no-mixed-requires": "error",
|
||||
"no-multi-assign": "error",
|
||||
"no-multi-spaces": "off",
|
||||
"no-multi-str": "error",
|
||||
"no-multiple-empty-lines": "error",
|
||||
"no-native-reassign": "error",
|
||||
"no-negated-condition": "off",
|
||||
"no-negated-in-lhs": "error",
|
||||
"no-nested-ternary": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-object": "error",
|
||||
"no-new-require": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-path-concat": "error",
|
||||
"no-plusplus": "error",
|
||||
"no-process-env": "error",
|
||||
"no-process-exit": "error",
|
||||
"no-proto": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-restricted-globals": "error",
|
||||
"no-restricted-imports": "error",
|
||||
"no-restricted-modules": "error",
|
||||
"no-restricted-properties": "error",
|
||||
"no-restricted-syntax": "error",
|
||||
"no-return-assign": "error",
|
||||
"no-return-await": "error",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": "off",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-spaced-func": "error",
|
||||
"no-sync": "error",
|
||||
"no-tabs": "off",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-ternary": "off",
|
||||
"no-throw-literal": "error",
|
||||
"no-trailing-spaces": "off",
|
||||
"no-undef-init": "error",
|
||||
"no-undefined": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-use-before-define": "error",
|
||||
"no-useless-call": "error",
|
||||
// "no-useless-catch": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-useless-return": "error",
|
||||
"no-var": "error",
|
||||
"no-void": "error",
|
||||
"no-warning-comments": "error",
|
||||
"no-whitespace-before-property": "error",
|
||||
"no-with": "error",
|
||||
"nonblock-statement-body-position": "error",
|
||||
"object-curly-newline": "error",
|
||||
"object-curly-spacing": "off",
|
||||
"object-shorthand": "off",
|
||||
"one-var": "off",
|
||||
"one-var-declaration-per-line": "error",
|
||||
"operator-assignment": "error",
|
||||
"operator-linebreak": "error",
|
||||
"padded-blocks": "off",
|
||||
"padding-line-between-statements": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"prefer-numeric-literals": "off",
|
||||
"prefer-object-spread": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"prefer-reflect": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
"quote-props": "off",
|
||||
"quotes": "off",
|
||||
"radix": "error",
|
||||
"require-atomic-updates": "error",
|
||||
"require-await": "off",
|
||||
"require-jsdoc": "off",
|
||||
"require-unicode-regexp": "off",
|
||||
"rest-spread-spacing": "error",
|
||||
"semi": "error",
|
||||
"semi-spacing": "error",
|
||||
"semi-style": [
|
||||
"error",
|
||||
"last"
|
||||
],
|
||||
"sort-imports": "error",
|
||||
"sort-keys": "off",
|
||||
"sort-vars": "error",
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": "off",
|
||||
"space-in-parens": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": [
|
||||
"error",
|
||||
{
|
||||
"nonwords": false,
|
||||
"words": false
|
||||
}
|
||||
],
|
||||
"spaced-comment": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"strict": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"switch-colon-spacing": "error",
|
||||
"symbol-description": "error",
|
||||
"template-curly-spacing": "error",
|
||||
"template-tag-spacing": "error",
|
||||
"unicode-bom": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"valid-jsdoc": "error",
|
||||
"vars-on-top": "error",
|
||||
"wrap-iife": "error",
|
||||
"wrap-regex": "error",
|
||||
"yield-star-spacing": "error",
|
||||
"yoda": "off"
|
||||
}
|
||||
};
|
@ -1,93 +0,0 @@
|
||||
export class ConnectionView {
|
||||
constructor(client, provider) {
|
||||
this._provider = provider;
|
||||
this._client = client;
|
||||
this._client.onopen = () => { this._onConnectionOpened(); };
|
||||
this._client.onclose = () => { this._onConnectionClosed(); };
|
||||
|
||||
this.element = document.createElement("div");
|
||||
|
||||
const connectBox = document.createElement("div");
|
||||
this.element.appendChild(connectBox);
|
||||
|
||||
const urlLabel = document.createElement("span");
|
||||
urlLabel.textContent = "URL:";
|
||||
connectBox.appendChild(urlLabel);
|
||||
|
||||
this.urlTextbox = document.createElement("input");
|
||||
this.urlTextbox.type = "text";
|
||||
this.urlTextbox.value = window.location.href.replace(/^http/, "ws");
|
||||
connectBox.appendChild(this.urlTextbox);
|
||||
|
||||
this.connectButton = document.createElement("input");
|
||||
this.connectButton.type = "button";
|
||||
this.connectButton.value = "connect";
|
||||
this.connectButton.addEventListener("click", () => { this._onConnectButtonClicked(); });
|
||||
connectBox.appendChild(this.connectButton);
|
||||
|
||||
|
||||
const authenticateBox = document.createElement("div");
|
||||
this.element.appendChild(authenticateBox);
|
||||
|
||||
const authLabel = document.createElement("span");
|
||||
authLabel.textContent = "use authentication:";
|
||||
authenticateBox.appendChild(authLabel);
|
||||
|
||||
this.authenticateCheckbox = document.createElement("input");
|
||||
this.authenticateCheckbox.type = "checkbox";
|
||||
authenticateBox.appendChild(this.authenticateCheckbox);
|
||||
|
||||
const usernameLabel = document.createElement("span");
|
||||
usernameLabel.textContent = "user:";
|
||||
authenticateBox.appendChild(usernameLabel);
|
||||
|
||||
this.usernameTextbox = document.createElement("input");
|
||||
this.usernameTextbox.type = "text";
|
||||
this.usernameTextbox.value = "bob";
|
||||
authenticateBox.appendChild(this.usernameTextbox);
|
||||
|
||||
const passwordLabel = document.createElement("span");
|
||||
passwordLabel.textContent = "user:";
|
||||
authenticateBox.appendChild(passwordLabel);
|
||||
|
||||
this.passwordTextbox = document.createElement("input");
|
||||
this.passwordTextbox.type = "password";
|
||||
this.passwordTextbox.value = "secret";
|
||||
authenticateBox.appendChild(this.passwordTextbox);
|
||||
}
|
||||
|
||||
_onConnectButtonClicked() {
|
||||
if (!this._client.isConnected()) {
|
||||
let url = this.urlTextbox.value;
|
||||
this._client.connectTo(url);
|
||||
}
|
||||
else {
|
||||
this._client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
_onAuthenticateButtonClicked() {
|
||||
if (this._client.isConnected()) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
_onConnectionOpened() {
|
||||
if (this.authenticateCheckbox.checked) {
|
||||
const username = this.usernameTextbox.value;
|
||||
const password = this.passwordTextbox.value;
|
||||
|
||||
const promise = this._client.authenticate("username", { username, password });
|
||||
promise.then(() => { this._client.addProvider("test", this._provider); });
|
||||
} else {
|
||||
this._client.addProvider("test", this._provider);
|
||||
}
|
||||
|
||||
this.connectButton.value = "disconnect";
|
||||
}
|
||||
|
||||
_onConnectionClosed() {
|
||||
this.connectButton.value = "connect";
|
||||
}
|
||||
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
|
||||
|
||||
import { BadState } from "./webfuse/bad_state.js";
|
||||
import { FileMode } from "./webfuse/file_mode.js";
|
||||
import { Provider } from "./webfuse/provider.js";
|
||||
|
||||
export class FileSystemProvider extends Provider {
|
||||
constructor(root) {
|
||||
super();
|
||||
|
||||
this.root = root;
|
||||
this._inodes = { };
|
||||
|
||||
this._walk(this.root, (entry) => { this._inodes[entry.inode] = entry; });
|
||||
}
|
||||
|
||||
_walk(node, callback) {
|
||||
callback(node);
|
||||
|
||||
const entries = node.entries;
|
||||
if (entries) {
|
||||
for(let entry of Object.entries(entries)) {
|
||||
this._walk(entry[1], callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async lookup(parent, name) {
|
||||
const parentEntry = this._inodes[parent];
|
||||
const entry = (parentEntry && parentEntry.entries && parentEntry.entries[name]) || null;
|
||||
if (entry) {
|
||||
return {
|
||||
inode: entry.inode,
|
||||
mode: entry.mode || parseInt("755", 8),
|
||||
type: entry.type || "file",
|
||||
size: entry.size || (entry.contents && entry.contents.length) || 0,
|
||||
atime: entry.atime || 0,
|
||||
mtime: entry.mtime || 0,
|
||||
ctime: entry.ctime || 0
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.NO_ENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getattr(inode) {
|
||||
let entry = this._inodes[inode];
|
||||
if (entry) {
|
||||
return {
|
||||
mode: entry.mode || parseInt("755", 8),
|
||||
type: entry.type || "file",
|
||||
size: entry.size || (entry.contents && entry.contents.length) || 0,
|
||||
atime: entry.atime || 0,
|
||||
mtime: entry.mtime || 0,
|
||||
ctime: entry.ctime || 0
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.NO_ENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
async readdir(inode) {
|
||||
let entry = this._inodes[inode];
|
||||
|
||||
if ((entry) && ("dir" === entry.type)) {
|
||||
let result = [
|
||||
{name: ".", inode: entry.inode},
|
||||
{name: "..", inode: entry.inode}
|
||||
];
|
||||
for(let subdir of Object.entries(entry.entries)) {
|
||||
const name = subdir[0];
|
||||
const inode = subdir[1].inode;
|
||||
result.push({name, inode});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.NO_ENTRY);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async open(inode, mode) {
|
||||
let entry = this._inodes[inode];
|
||||
|
||||
if (entry.type === "file") {
|
||||
if ((mode & FileMode.ACCESS_MODE) === FileMode.READONLY) {
|
||||
return {handle: 1337};
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.NO_ACCESS);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.NO_ENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
close(_inode, _handle, _mode) {
|
||||
// do nothing
|
||||
return true;
|
||||
}
|
||||
|
||||
async read(inode, handle, offset, length) {
|
||||
let entry = this._inodes[inode];
|
||||
|
||||
if (entry.type === "file") {
|
||||
const end = Math.min(offset + length, entry.contents.length);
|
||||
const data = (offset < entry.contents.length) ? entry.contents.substring(offset, end) : "";
|
||||
|
||||
return data;
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.NO_ENTRY);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "webfuse-provider",
|
||||
"version": "0.2.0",
|
||||
"description": "Provider for websocket filesystem (webfuse)",
|
||||
"main": "startup.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Falk Werner",
|
||||
"license": "LGPL-3.0"
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import { Client } from "./webfuse/client.js";
|
||||
import { ConnectionView } from "./connection_view.js";
|
||||
import { FileSystemProvider } from "./filesystem_provider.js";
|
||||
|
||||
|
||||
function mode(value) {
|
||||
return parseInt(value, 8);
|
||||
}
|
||||
|
||||
function startup() {
|
||||
const provider = new FileSystemProvider({
|
||||
inode: 1,
|
||||
mode: mode("0755"),
|
||||
type: "dir",
|
||||
entries: {
|
||||
"hello.txt" : { inode: 2, mode: mode("0444"), type: "file", contents: "Hello, World!"},
|
||||
"say_hello.sh": { inode: 3, mode: mode("0555"), type: "file", contents: "#!/bin/sh\necho hello\n"}
|
||||
}
|
||||
});
|
||||
const client = new Client();
|
||||
const connectionView = new ConnectionView(client, provider);
|
||||
document.getElementById('connection').appendChild(connectionView.element);
|
||||
}
|
||||
|
||||
window.onload = startup;
|
@ -1,15 +0,0 @@
|
||||
export class BadState extends Error {
|
||||
static get BAD() { return 1; }
|
||||
|
||||
static get NOT_IMPLEMENTED() { return 2; }
|
||||
static get TIMEOUT() { return 3; }
|
||||
static get FORMAT() { return 4; }
|
||||
|
||||
static get NO_ENTRY() { return 101; }
|
||||
static get NO_ACCESS() { return 102; }
|
||||
|
||||
constructor(code) {
|
||||
super("Bad State");
|
||||
this.code = code;
|
||||
}
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
import { BadState } from "./bad_state.js";
|
||||
|
||||
export class Client {
|
||||
static get _PROTOCOL() { return "fs"; }
|
||||
|
||||
constructor(provider) {
|
||||
this._provider = { };
|
||||
this._pendingRequests = {};
|
||||
this._id = 0;
|
||||
this._ws = null;
|
||||
this.onopen = () => { };
|
||||
this.onclose = () => { };
|
||||
this.onerror = () => { };
|
||||
}
|
||||
|
||||
connectTo(url) {
|
||||
this.disconnect();
|
||||
|
||||
this._ws = new WebSocket(url, Client._PROTOCOL);
|
||||
this._ws.onopen = this.onopen;
|
||||
this._ws.onclose = this.onclose;
|
||||
this._ws.onerror = this.onerror;
|
||||
|
||||
this._ws.onmessage = (message) => {
|
||||
this._onmessage(message);
|
||||
};
|
||||
}
|
||||
|
||||
_invokeRequest(method, params) {
|
||||
this._id += 1;
|
||||
const id = this._id;
|
||||
const request = {method, params, id};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._pendingRequests[id] = {resolve, reject};
|
||||
this._ws.send(JSON.stringify(request));
|
||||
});
|
||||
}
|
||||
|
||||
authenticate(type, credentials) {
|
||||
return this._invokeRequest("authenticate", [type, credentials]);
|
||||
}
|
||||
|
||||
addProvider(name, provider) {
|
||||
this._provider[name] = provider;
|
||||
const request = {
|
||||
"method": "add_filesystem",
|
||||
"params": [name],
|
||||
"id": 23
|
||||
};
|
||||
|
||||
this._ws.send(JSON.stringify(request));
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._ws) {
|
||||
this._ws.close();
|
||||
this._ws = null;
|
||||
}
|
||||
}
|
||||
|
||||
isConnected() {
|
||||
return ((this._ws) && (this._ws.readyState === WebSocket.OPEN));
|
||||
}
|
||||
|
||||
_isRequest(request) {
|
||||
const method = request.method;
|
||||
|
||||
return (("string" === typeof(method)) && ("params" in request));
|
||||
}
|
||||
|
||||
_isResponse(response) {
|
||||
const id = response.id;
|
||||
|
||||
return (("number" === typeof(id)) && (("result" in response) || ("error" in response)));
|
||||
}
|
||||
|
||||
_removePendingRequest(id) {
|
||||
let result = null;
|
||||
|
||||
if (id in this._pendingRequests) {
|
||||
result = this._pendingRequests[id];
|
||||
Reflect.deleteProperty(this._pendingRequests, id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
_onmessage(message) {
|
||||
try {
|
||||
const data = JSON.parse(message.data);
|
||||
|
||||
if (this._isRequest(data)) {
|
||||
const method = data.method;
|
||||
const id = data.id;
|
||||
const params = data.params;
|
||||
|
||||
if ("number" === typeof(id)) {
|
||||
this._invoke(method, params, id);
|
||||
}
|
||||
else {
|
||||
this._notify(method, params);
|
||||
}
|
||||
}
|
||||
else if (this._isResponse(data)) {
|
||||
const id = data.id;
|
||||
const result = data.result;
|
||||
const error = data.error;
|
||||
|
||||
const request = this._removePendingRequest(id);
|
||||
if (request) {
|
||||
if (result) {
|
||||
request.resolve(result);
|
||||
}
|
||||
else {
|
||||
request.reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (ex) {
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
|
||||
_invoke(method, params, id) {
|
||||
this._invokeAsync(method, params).
|
||||
then((result) => {
|
||||
const response = { result, id };
|
||||
this._ws.send(JSON.stringify(response));
|
||||
}).
|
||||
catch((ex) => {
|
||||
const code = ex.code || BadState.BAD;
|
||||
const response = {error: {code}, id};
|
||||
this._ws.send(JSON.stringify(response));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async _invokeAsync(method, params) {
|
||||
switch(method)
|
||||
{
|
||||
case "lookup":
|
||||
return this._lookup(params);
|
||||
case "getattr":
|
||||
return this._getattr(params);
|
||||
case "readdir":
|
||||
return this._readdir(params);
|
||||
case "open":
|
||||
return this._open(params);
|
||||
case "read":
|
||||
return this._read(params);
|
||||
default:
|
||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
||||
}
|
||||
}
|
||||
|
||||
_notify(method, params) {
|
||||
switch(method) {
|
||||
case 'close':
|
||||
this._close(params);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid method: "${method}"`);
|
||||
}
|
||||
}
|
||||
|
||||
_getProvider(name) {
|
||||
if (name in this._provider) {
|
||||
return this._provider[name];
|
||||
}
|
||||
else {
|
||||
throw new Error('Unknown provider');
|
||||
}
|
||||
}
|
||||
|
||||
async _lookup([providerName, parent, name]) {
|
||||
const provider = this._getProvider(providerName);
|
||||
|
||||
return provider.lookup(parent, name);
|
||||
}
|
||||
|
||||
async _getattr([providerName, inode]) {
|
||||
const provider = this._getProvider(providerName);
|
||||
|
||||
return provider.getattr(inode);
|
||||
}
|
||||
|
||||
async _readdir([providerName, inode]) {
|
||||
const provider = this._getProvider(providerName);
|
||||
|
||||
return provider.readdir(inode);
|
||||
}
|
||||
|
||||
async _open([providerName, inode, mode]) {
|
||||
const provider = this._getProvider(providerName);
|
||||
|
||||
return provider.open(inode, mode);
|
||||
}
|
||||
|
||||
_close([providerName, inode, handle, mode]) {
|
||||
const provider = this._getProvider(providerName);
|
||||
|
||||
provider.close(inode, handle, mode);
|
||||
}
|
||||
|
||||
async _read([providerName, inode, handle, offset, length]) {
|
||||
const provider = this._getProvider(providerName);
|
||||
const data = await provider.read(inode, handle, offset, length);
|
||||
|
||||
if ("string" === typeof(data)) {
|
||||
return {
|
||||
data: btoa(data),
|
||||
format: "base64",
|
||||
count: data.length
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw new BadState(BadState.BAD);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
export class FileMode {
|
||||
static get ACCESS_MODE() { return 0x003; }
|
||||
static get READONLY() { return 0x000; }
|
||||
static get WRITEONLY() { return 0x001; }
|
||||
static get READWRITE() { return 0x002; }
|
||||
static get CREATE() { return 0x040; }
|
||||
static get EXCLUSIVE() { return 0x080; }
|
||||
static get TRUNKATE() { return 0x200; }
|
||||
static get APPEND() { return 0x400; }
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
|
||||
|
||||
import { BadState } from "./bad_state.js";
|
||||
|
||||
export class Provider {
|
||||
|
||||
async lookup(_parent, _name) {
|
||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
async getattr(_inode) {
|
||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
async readdir(_inode) {
|
||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
async open(_inode, _mode) {
|
||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
close(_inode, _handle, _mode) {
|
||||
// empty
|
||||
}
|
||||
|
||||
async read(_inode, _handle, _offset, _length) {
|
||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
html, body {
|
||||
font-family: monospace;
|
||||
background-color: #c0c0c0;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin-left: 50px;
|
||||
margin-right: 50px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.window {
|
||||
border: 1px solid black;
|
||||
background-color: black;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 25px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.window .title {
|
||||
text-align: center;
|
||||
color: #dba329;
|
||||
font-weight: bold;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #dba329;
|
||||
}
|
||||
|
||||
.commands {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.content {
|
||||
column-count: 2;
|
||||
column-width: 50%;
|
||||
}
|
||||
|
||||
.content > div {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#connection {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
#ifndef USERDB_H
|
||||
#define USERDB_H
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
struct userdb;
|
||||
|
||||
extern struct userdb * userdb_create(
|
||||
char const * pepper);
|
||||
|
||||
extern void userdb_dispose(struct userdb * db);
|
||||
|
||||
extern bool userdb_save(
|
||||
struct userdb * db,
|
||||
char const * filename);
|
||||
|
||||
extern bool userdb_load(
|
||||
struct userdb * db,
|
||||
char const * filename);
|
||||
|
||||
extern void userdb_add(
|
||||
struct userdb * db,
|
||||
char const * username,
|
||||
char const * password);
|
||||
|
||||
extern void userdb_remove(
|
||||
struct userdb * db,
|
||||
char const * user);
|
||||
|
||||
extern bool userdb_check(
|
||||
struct userdb * db,
|
||||
char const * username,
|
||||
char const * password);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,277 +0,0 @@
|
||||
#include "userdb.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#define USERDB_HASH_ALGORITHM "sha512"
|
||||
#define USERDB_MAJOR 1
|
||||
#define USERDB_MINOR 0
|
||||
|
||||
#define USERDB_SALT_SIZE 32
|
||||
|
||||
struct userdb
|
||||
{
|
||||
json_t * users;
|
||||
char * pepper;
|
||||
char * hash_algorithm;
|
||||
};
|
||||
|
||||
static bool is_compatible(json_t * meta)
|
||||
{
|
||||
bool result = false;
|
||||
if (json_is_object(meta))
|
||||
{
|
||||
json_t * type = json_object_get(meta, "type");
|
||||
json_t * major = json_object_get(meta, "major");
|
||||
json_t * minor = json_object_get(meta, "minor");
|
||||
json_t * hash_algorithm = json_object_get(meta, "hash_algorithm");
|
||||
|
||||
result = (
|
||||
json_is_string(type) &&
|
||||
(0 == strcmp(json_string_value(type), "wf-userdb")) &&
|
||||
json_is_integer(major) &&
|
||||
(USERDB_MAJOR == json_integer_value(major)) &&
|
||||
json_is_integer(minor) &&
|
||||
json_is_string(hash_algorithm)
|
||||
);
|
||||
|
||||
if (result)
|
||||
{
|
||||
char const * algorithm = json_string_value(hash_algorithm);
|
||||
result = (NULL != EVP_get_digestbyname(algorithm));
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static char hex_char(unsigned char value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0x00: return '0';
|
||||
case 0x01: return '1';
|
||||
case 0x02: return '2';
|
||||
case 0x03: return '3';
|
||||
case 0x04: return '4';
|
||||
case 0x05: return '5';
|
||||
case 0x06: return '6';
|
||||
case 0x07: return '7';
|
||||
case 0x08: return '8';
|
||||
case 0x09: return '9';
|
||||
case 0x0a: return 'a';
|
||||
case 0x0b: return 'b';
|
||||
case 0x0c: return 'c';
|
||||
case 0x0d: return 'd';
|
||||
case 0x0e: return 'e';
|
||||
case 0x0f: return 'f';
|
||||
default: return '?';
|
||||
}
|
||||
}
|
||||
|
||||
static char * to_hex(unsigned char const * value, size_t length)
|
||||
{
|
||||
char * result = malloc((2 * length) + 1);
|
||||
if (NULL != result)
|
||||
{
|
||||
for (size_t i = 0, j = 0; i < length; i++, j+=2)
|
||||
{
|
||||
unsigned char high = (value[i] >> 4) & 0x0f;
|
||||
unsigned char low = value[i] & 0x0f;
|
||||
|
||||
result[j ] = hex_char(high);
|
||||
result[j + 1] = hex_char(low);
|
||||
}
|
||||
|
||||
result[2 * length] = '\0';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static char * generate_salt(void)
|
||||
{
|
||||
unsigned char buffer[USERDB_SALT_SIZE];
|
||||
int rc = RAND_bytes(buffer, USERDB_SALT_SIZE);
|
||||
if (1 != rc)
|
||||
{
|
||||
fprintf(stderr, "fatal: failed to generate salt (OpenSSL RAND_bytes failed)\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return to_hex(buffer, USERDB_SALT_SIZE);
|
||||
}
|
||||
|
||||
static char * compute_hash(
|
||||
struct userdb * db,
|
||||
char const * password,
|
||||
char const * salt)
|
||||
{
|
||||
EVP_MD const * digest = EVP_get_digestbyname(db->hash_algorithm);
|
||||
if (NULL == digest)
|
||||
{
|
||||
fprintf(stderr, "error: hash algorithm %s not supported\n", db->hash_algorithm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char * result = NULL;
|
||||
unsigned int hash_size = EVP_MD_size(digest);
|
||||
unsigned char * hash = malloc(hash_size);
|
||||
|
||||
if (NULL != hash)
|
||||
{
|
||||
EVP_MD_CTX * context = EVP_MD_CTX_new();
|
||||
EVP_DigestInit_ex(context, digest, NULL);
|
||||
EVP_DigestUpdate(context, password, strlen(password));
|
||||
EVP_DigestUpdate(context, salt, strlen(salt));
|
||||
EVP_DigestUpdate(context, db->pepper, strlen(db->pepper));
|
||||
EVP_DigestFinal_ex(context, hash, &hash_size);
|
||||
EVP_MD_CTX_free(context);
|
||||
|
||||
result = to_hex(hash, hash_size);
|
||||
free(hash);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct userdb * userdb_create(
|
||||
char const * pepper)
|
||||
{
|
||||
struct userdb * db = malloc(sizeof(struct userdb));
|
||||
if (NULL != db)
|
||||
{
|
||||
db->users = json_object();
|
||||
db->pepper = strdup(pepper);
|
||||
db->hash_algorithm = strdup(USERDB_HASH_ALGORITHM);
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
void userdb_dispose(
|
||||
struct userdb * db)
|
||||
{
|
||||
json_decref(db->users);
|
||||
free(db->pepper);
|
||||
free(db->hash_algorithm);
|
||||
free(db);
|
||||
}
|
||||
|
||||
bool userdb_save(
|
||||
struct userdb * db,
|
||||
char const * filename)
|
||||
{
|
||||
json_t * container = json_object();
|
||||
|
||||
json_t * meta = json_object();
|
||||
json_object_set_new(meta, "type", json_string("wf-userdb"));
|
||||
json_object_set_new(meta, "major", json_integer(USERDB_MAJOR));
|
||||
json_object_set_new(meta, "minor", json_integer(USERDB_MINOR));
|
||||
json_object_set_new(meta, "hash_algorithm", json_string(db->hash_algorithm));
|
||||
json_object_set_new(container, "meta", meta);
|
||||
|
||||
json_object_set(container, "users", db->users);
|
||||
|
||||
int result = json_dump_file(container, filename, JSON_INDENT(2));
|
||||
json_decref(container);
|
||||
|
||||
return (0 == result);
|
||||
}
|
||||
|
||||
|
||||
bool userdb_load(
|
||||
struct userdb * db,
|
||||
char const * filename)
|
||||
{
|
||||
bool result = false;
|
||||
json_t * container = json_load_file(filename, 0, NULL);
|
||||
if (NULL != container)
|
||||
{
|
||||
json_t * meta = json_object_get(container, "meta");
|
||||
json_t * users = json_object_get(container, "users");
|
||||
|
||||
if ((is_compatible(meta)) && (json_is_object(users))) {
|
||||
json_t * hash_algorithm = json_object_get(meta, "hash_algorithm");
|
||||
free(db->hash_algorithm);
|
||||
db->hash_algorithm = strdup(json_string_value(hash_algorithm));
|
||||
|
||||
json_decref(db->users);
|
||||
json_incref(users);
|
||||
db->users = users;
|
||||
|
||||
result = true;
|
||||
}
|
||||
|
||||
json_decref(container);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void userdb_add(
|
||||
struct userdb * db,
|
||||
char const * username,
|
||||
char const * password)
|
||||
{
|
||||
char * salt = generate_salt();
|
||||
char * hash = compute_hash(db, password, salt);
|
||||
|
||||
json_t * user = json_object();
|
||||
json_object_set_new(user, "password_hash", json_string(hash));
|
||||
json_object_set_new(user, "salt", json_string(salt));
|
||||
|
||||
json_object_set_new(db->users, username, user);
|
||||
|
||||
free(salt);
|
||||
free(hash);
|
||||
}
|
||||
|
||||
void userdb_remove(
|
||||
struct userdb * db,
|
||||
char const * user)
|
||||
{
|
||||
json_object_del(db->users, user);
|
||||
}
|
||||
|
||||
static char const * json_object_get_string(
|
||||
json_t * object,
|
||||
char const * key)
|
||||
{
|
||||
char const * result = NULL;
|
||||
|
||||
json_t * string_holder = json_object_get(object, key);
|
||||
if (json_is_string(string_holder))
|
||||
{
|
||||
result = json_string_value(string_holder);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool userdb_check(
|
||||
struct userdb * db,
|
||||
char const * username,
|
||||
char const * password)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
json_t * user = json_object_get(db->users, username);
|
||||
if (json_is_object(user))
|
||||
{
|
||||
char const * salt = json_object_get_string(user, "salt");
|
||||
char const * hash = json_object_get_string(user, "password_hash");
|
||||
|
||||
char * computed_hash = compute_hash(db, password, salt);
|
||||
|
||||
result = (0 == strcmp(computed_hash, hash));
|
||||
free(computed_hash);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
@ -1,304 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/opensslv.h>
|
||||
#include <openssl/engine.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <jansson.h>
|
||||
#include <userdb.h>
|
||||
|
||||
|
||||
struct args
|
||||
{
|
||||
char * file;
|
||||
char * command;
|
||||
char * username;
|
||||
char * password;
|
||||
char * pepper;
|
||||
bool show_help;
|
||||
};
|
||||
|
||||
typedef int command_invoke_fn(
|
||||
struct args * args);
|
||||
|
||||
struct command
|
||||
{
|
||||
char const * name;
|
||||
command_invoke_fn * invoke;
|
||||
};
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf(
|
||||
"webfuse-passwd, Copyright (c) 2019, webfuse authors <https://github.com/falk-werner/webfuse>\n"
|
||||
"Manage webfuse passwd file\n"
|
||||
"\n"
|
||||
"Usage: webfuse-passwd -f <file> -c <command> [-u <username>] [-p <password>] [-P <pepper>]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
"\t-f, --file Path of wf passwd file\n"
|
||||
"\t-c, --command Command to execute\n"
|
||||
"\t-u, --username Name of user\n"
|
||||
"\t-p, --password Password of user\n"
|
||||
"\t-P, --pepper pepper\n"
|
||||
"\t-h, --help Shows this message\n"
|
||||
"\n"
|
||||
"Commands:\n"
|
||||
"\tcreate Creates an empty passwd file (or cleans an existing)\n"
|
||||
"\t Example: webfuse-passwd -f passwd.json -c create\n"
|
||||
"\tadd Adds or replaces a user\n"
|
||||
"\t Example: webfuse-passwd -f passwd.json -c add -u bob -p secret\n"
|
||||
"\tremove Removes a user\n"
|
||||
"\t Example: webfuse-passwd -f passwd.json -c remove -u bob\n"
|
||||
"\tcheck Checks password of a user\n"
|
||||
"\t Example: webfuse-passwd -f passwd.json -c check -u bob -p secret\n"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
static int parse_args(struct args * args, int argc, char * argv[])
|
||||
{
|
||||
static struct option const options[] =
|
||||
{
|
||||
{"file", required_argument, NULL, 'f'},
|
||||
{"command", required_argument, NULL, 'c'},
|
||||
{"username", required_argument, NULL, 'u'},
|
||||
{"password", required_argument, NULL, 'p'},
|
||||
{"Pepper", required_argument, NULL, 'P'},
|
||||
{"help", required_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
int result = EXIT_SUCCESS;
|
||||
bool finished = false;
|
||||
while ((!finished) && (EXIT_SUCCESS == result))
|
||||
{
|
||||
int option_index = 0;
|
||||
int const c = getopt_long(argc, argv, "f:c:u:p:P:h", options, &option_index);
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case -1:
|
||||
finished = true;
|
||||
break;
|
||||
case 'h':
|
||||
args->show_help = true;
|
||||
finished = true;
|
||||
break;
|
||||
case 'f':
|
||||
free(args->file);
|
||||
args->file = strdup(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
free(args->command);
|
||||
args->command = strdup(optarg);
|
||||
break;
|
||||
case 'u':
|
||||
free(args->username);
|
||||
args->username = strdup(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
free(args->password);
|
||||
args->password = strdup(optarg);
|
||||
break;
|
||||
case 'P':
|
||||
free(args->pepper);
|
||||
args->pepper = strdup(optarg);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "error: unknown argument\n");
|
||||
result = EXIT_FAILURE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((result == EXIT_SUCCESS) && (!args->show_help))
|
||||
{
|
||||
if (NULL == args->file)
|
||||
{
|
||||
fprintf(stderr, "error: missing file\n");
|
||||
args->show_help = true;
|
||||
result = EXIT_FAILURE;
|
||||
}
|
||||
else if (NULL == args->command)
|
||||
{
|
||||
fprintf(stderr, "error: missing command\n");
|
||||
args->show_help = true;
|
||||
result = EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void args_init(struct args * args)
|
||||
{
|
||||
args->file = NULL;
|
||||
args->command = NULL;
|
||||
args->username = NULL;
|
||||
args->password = NULL;
|
||||
args->pepper = strdup("");
|
||||
args->show_help = false;
|
||||
}
|
||||
|
||||
static int create_passwd(struct args * args)
|
||||
{
|
||||
struct userdb * db = userdb_create(args->pepper);
|
||||
bool result = userdb_save(db, args->file);
|
||||
userdb_dispose(db);
|
||||
|
||||
return (result) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
static int add_user(struct args * args)
|
||||
{
|
||||
if (NULL == args->username)
|
||||
{
|
||||
fprintf(stderr, "error: missing username");
|
||||
args->show_help = true;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (NULL == args->password)
|
||||
{
|
||||
fprintf(stderr, "error: missing password");
|
||||
args->show_help = true;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
struct userdb * db = userdb_create(args->pepper);
|
||||
userdb_load(db, args->file);
|
||||
userdb_add(db, args->username, args->password);
|
||||
bool result = userdb_save(db, args->file);
|
||||
userdb_dispose(db);
|
||||
|
||||
return (result) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
static int remove_user(struct args * args)
|
||||
{
|
||||
if (NULL == args->username)
|
||||
{
|
||||
fprintf(stderr, "error: missing username");
|
||||
args->show_help = true;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
struct userdb * db = userdb_create(args->pepper);
|
||||
userdb_load(db, args->file);
|
||||
userdb_remove(db, args->username);
|
||||
bool result = userdb_save(db, args->file);
|
||||
userdb_dispose(db);
|
||||
|
||||
return (result) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
static int check_password(struct args * args)
|
||||
{
|
||||
if (NULL == args->username)
|
||||
{
|
||||
fprintf(stderr, "error: missing username");
|
||||
args->show_help = true;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (NULL == args->password)
|
||||
{
|
||||
fprintf(stderr, "error: missing password");
|
||||
args->show_help = true;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
struct userdb * db = userdb_create(args->pepper);
|
||||
userdb_load(db, args->file);
|
||||
bool result = userdb_check(db, args->username, args->password);
|
||||
userdb_dispose(db);
|
||||
|
||||
printf("%s\n", (result) ? "OK" : "FAILURE");
|
||||
return (result) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
static int invoke_invalid_command(struct args * args)
|
||||
{
|
||||
(void) args;
|
||||
|
||||
fprintf(stderr, "error: unknown command\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
static struct command const commands[] =
|
||||
{
|
||||
{"create", &create_passwd},
|
||||
{"add", &add_user},
|
||||
{"remove", &remove_user},
|
||||
{"check", &check_password},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
static struct command const invalid_command =
|
||||
{
|
||||
"<invalid>",
|
||||
&invoke_invalid_command
|
||||
};
|
||||
|
||||
static struct command const * get_command(char const * name)
|
||||
{
|
||||
for(size_t i = 0; NULL != commands[i].name; i++)
|
||||
{
|
||||
if (0 == strcmp(name, commands[i].name))
|
||||
{
|
||||
return &commands[i];
|
||||
}
|
||||
}
|
||||
|
||||
return &invalid_command;
|
||||
}
|
||||
|
||||
static void args_cleanup(struct args * args)
|
||||
{
|
||||
free(args->file);
|
||||
free(args->command);
|
||||
free(args->username);
|
||||
free(args->password);
|
||||
free(args->pepper);
|
||||
}
|
||||
|
||||
static void openssl_cleanup(void)
|
||||
{
|
||||
FIPS_mode_set(0);
|
||||
ENGINE_cleanup();
|
||||
CONF_modules_unload(1);
|
||||
EVP_cleanup();
|
||||
CRYPTO_cleanup_all_ex_data();
|
||||
ERR_free_strings();
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
OPENSSL_init();
|
||||
OPENSSL_add_all_algorithms_conf();
|
||||
|
||||
struct args args;
|
||||
args_init(&args);
|
||||
int result = parse_args(&args, argc, argv);
|
||||
if ((EXIT_SUCCESS == result) && (!args.show_help))
|
||||
{
|
||||
struct command const * command = get_command(args.command);
|
||||
result = command->invoke(&args);
|
||||
}
|
||||
|
||||
if (args.show_help)
|
||||
{
|
||||
print_usage();
|
||||
}
|
||||
|
||||
args_cleanup(&args);
|
||||
openssl_cleanup();
|
||||
return result;
|
||||
}
|
@ -1,385 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "webfuse_provider.h"
|
||||
|
||||
#define SERVICE_TIMEOUT (1 * 1000)
|
||||
|
||||
struct config
|
||||
{
|
||||
char * url;
|
||||
struct wfp_client_config * client_config;
|
||||
bool show_help;
|
||||
};
|
||||
|
||||
enum fs_entry_type
|
||||
{
|
||||
FS_FILE,
|
||||
FS_DIR
|
||||
};
|
||||
|
||||
struct fs_entry
|
||||
{
|
||||
ino_t parent;
|
||||
ino_t inode;
|
||||
char const * name;
|
||||
int mode;
|
||||
enum fs_entry_type type;
|
||||
size_t content_length;
|
||||
char const * content;
|
||||
};
|
||||
|
||||
struct fs
|
||||
{
|
||||
struct fs_entry const * entries;
|
||||
};
|
||||
|
||||
static void show_help()
|
||||
{
|
||||
printf(
|
||||
"webfuse-provider, Copyright (c) 2019, webfuse authors <https://github.com/falk-werner/webfuse>\n"
|
||||
"Example for websocket file system provider\n"
|
||||
"\n"
|
||||
"Usage: webfuse-provider -u <url> [-k <key_path>] [-c <cert_path>]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
"\t-u, --url URL of webfuse server (required)\n"
|
||||
"\t-k, --key_path Path to private key of provider (default: not set, TLS disabled)\n"
|
||||
"\t-c, --cert_path Path to certificate of provider (defautl: not set, TLS disabled)\n"
|
||||
"\t-h, --help prints this message\n"
|
||||
"\n"
|
||||
"Example:\n"
|
||||
"\twebfuse-provider -u ws://localhost:8080/\n"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
static int parse_arguments(
|
||||
int argc,
|
||||
char* argv[],
|
||||
struct config * config)
|
||||
{
|
||||
static struct option const options[] =
|
||||
{
|
||||
{"url", required_argument, NULL, 'u'},
|
||||
{"key_path", required_argument, NULL, 'k'},
|
||||
{"cert_path", required_argument, NULL, 'c'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
int result = EXIT_SUCCESS;
|
||||
bool finished = false;
|
||||
while (!finished)
|
||||
{
|
||||
int option_index = 0;
|
||||
int const c = getopt_long(argc, argv, "u:k:c:h", options, &option_index);
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case -1:
|
||||
finished = true;
|
||||
break;
|
||||
case 'h':
|
||||
config->show_help = true;
|
||||
finished = true;
|
||||
break;
|
||||
case 'u':
|
||||
free(config->url);
|
||||
config->url = strdup(optarg);
|
||||
break;
|
||||
case 'k':
|
||||
wfp_client_config_set_keypath(config->client_config, optarg);
|
||||
break;
|
||||
case 'c':
|
||||
wfp_client_config_set_certpath(config->client_config, optarg);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "error: unknown argument\n");
|
||||
finished = true;
|
||||
result = EXIT_FAILURE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (NULL == config->url)
|
||||
{
|
||||
fprintf(stderr, "error: missing required argument \"-u\"\n");
|
||||
result = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (result != EXIT_SUCCESS)
|
||||
{
|
||||
config->show_help = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct fs_entry const * fs_getentry(
|
||||
struct fs * fs,
|
||||
ino_t inode)
|
||||
{
|
||||
for (size_t i = 0; 0 != fs->entries[i].inode; i++)
|
||||
{
|
||||
struct fs_entry const * entry = &fs->entries[i];
|
||||
if (inode == entry->inode)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct fs_entry const * fs_getentry_byname(
|
||||
struct fs * fs,
|
||||
ino_t parent,
|
||||
char const * name)
|
||||
{
|
||||
for( size_t i = 0; 0 != fs->entries[i].inode; i++)
|
||||
{
|
||||
struct fs_entry const * entry = &fs->entries[i];
|
||||
if ((parent == entry->parent) && (0 == strcmp(name, entry->name)))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void fs_stat(
|
||||
struct fs_entry const * entry,
|
||||
struct stat * stat)
|
||||
{
|
||||
memset(stat, 0, sizeof(struct stat));
|
||||
|
||||
stat->st_ino = entry->inode;
|
||||
stat->st_mode = entry->mode;
|
||||
|
||||
if (FS_DIR == entry->type)
|
||||
{
|
||||
stat->st_mode |= S_IFDIR;
|
||||
}
|
||||
|
||||
if (FS_FILE == entry->type)
|
||||
{
|
||||
stat->st_mode |= S_IFREG;
|
||||
stat->st_size = entry->content_length;
|
||||
}
|
||||
}
|
||||
|
||||
static void fs_lookup(
|
||||
struct wfp_request * request,
|
||||
ino_t parent,
|
||||
char const * name,
|
||||
void * user_data)
|
||||
{
|
||||
struct fs * fs = (struct fs*) user_data;
|
||||
struct fs_entry const * entry = fs_getentry_byname(fs, parent, name);
|
||||
if (NULL != entry)
|
||||
{
|
||||
struct stat stat;
|
||||
fs_stat(entry, &stat);
|
||||
|
||||
wfp_respond_lookup(request, &stat);
|
||||
}
|
||||
else
|
||||
{
|
||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void fs_getattr(
|
||||
struct wfp_request * request,
|
||||
ino_t inode,
|
||||
void * user_data)
|
||||
{
|
||||
struct fs * fs = (struct fs*) user_data;
|
||||
struct fs_entry const * entry = fs_getentry(fs, inode);
|
||||
|
||||
if (NULL != entry)
|
||||
{
|
||||
struct stat stat;
|
||||
fs_stat(entry, &stat);
|
||||
|
||||
wfp_respond_getattr(request, &stat);
|
||||
}
|
||||
else
|
||||
{
|
||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
static void fs_readdir(
|
||||
struct wfp_request * request,
|
||||
ino_t directory,
|
||||
void * user_data)
|
||||
{
|
||||
struct fs * fs = (struct fs*) user_data;
|
||||
|
||||
struct fs_entry const * dir = fs_getentry(fs, directory);
|
||||
if ((NULL != dir) && (FS_DIR == dir->type))
|
||||
{
|
||||
struct wfp_dirbuffer * buffer = wfp_dirbuffer_create();
|
||||
wfp_dirbuffer_add(buffer, ".", dir->inode);
|
||||
wfp_dirbuffer_add(buffer, "..", dir->inode);
|
||||
|
||||
for(size_t i = 0; 0 != fs->entries[i].inode; i++)
|
||||
{
|
||||
struct fs_entry const * entry = &fs->entries[i];
|
||||
if (directory == entry->parent)
|
||||
{
|
||||
wfp_dirbuffer_add(buffer, entry->name, entry->inode);
|
||||
}
|
||||
}
|
||||
|
||||
wfp_respond_readdir(request, buffer);
|
||||
wfp_dirbuffer_dispose(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
static void fs_open(
|
||||
struct wfp_request * request,
|
||||
ino_t inode,
|
||||
int flags,
|
||||
void * user_data)
|
||||
{
|
||||
struct fs * fs = (struct fs*) user_data;
|
||||
|
||||
struct fs_entry const * entry = fs_getentry(fs, inode);
|
||||
if ((NULL != entry) && (FS_FILE == entry->type))
|
||||
{
|
||||
if (O_RDONLY == (flags & O_ACCMODE))
|
||||
{
|
||||
wfp_respond_open(request, 0U);
|
||||
}
|
||||
else
|
||||
{
|
||||
wfp_respond_error(request, WF_BAD_ACCESS_DENIED);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t min(size_t const a, size_t const b)
|
||||
{
|
||||
return (a < b) ? a : b;
|
||||
}
|
||||
|
||||
static void fs_read(
|
||||
struct wfp_request * request,
|
||||
ino_t inode,
|
||||
uint32_t handle,
|
||||
size_t offset,
|
||||
size_t length,
|
||||
void * user_data)
|
||||
{
|
||||
(void) handle;
|
||||
|
||||
struct fs * fs = (struct fs*) user_data;
|
||||
struct fs_entry const * entry = fs_getentry(fs, inode);
|
||||
if ((NULL != entry) && (FS_FILE == entry->type))
|
||||
{
|
||||
if (entry->content_length > offset)
|
||||
{
|
||||
size_t const remaining = entry->content_length - offset;
|
||||
size_t const count = min(remaining, length);
|
||||
|
||||
wfp_respond_read(request, &entry->content[offset], count);
|
||||
}
|
||||
else
|
||||
{
|
||||
wfp_respond_error(request, WF_BAD);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
static volatile bool shutdown_requested = false;
|
||||
|
||||
static void on_interrupt(int signal_id)
|
||||
{
|
||||
(void) signal_id;
|
||||
shutdown_requested = true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
struct config config;
|
||||
config.url = NULL;
|
||||
config.show_help = false;
|
||||
config.client_config = wfp_client_config_create();
|
||||
int result = parse_arguments(argc, argv, &config);
|
||||
|
||||
if (EXIT_SUCCESS == result)
|
||||
{
|
||||
static struct fs_entry const entries[]=
|
||||
{
|
||||
{.parent = 0, .inode = 1, .name = "<root>", .mode = 0555, .type = FS_DIR},
|
||||
{
|
||||
.parent = 1,
|
||||
.inode = 2,
|
||||
.name = "hello.txt",
|
||||
.mode = 0444,
|
||||
.type = FS_FILE,
|
||||
.content="hello, world!",
|
||||
.content_length = 13,
|
||||
},
|
||||
{.parent = 0, .inode = 0, .name = NULL}
|
||||
};
|
||||
|
||||
struct fs fs =
|
||||
{
|
||||
.entries = entries
|
||||
};
|
||||
|
||||
signal(SIGINT, &on_interrupt);
|
||||
|
||||
wfp_client_config_set_userdata(config.client_config, &fs);
|
||||
wfp_client_config_set_onlookup(config.client_config, &fs_lookup);
|
||||
wfp_client_config_set_ongetattr(config.client_config, &fs_getattr);
|
||||
wfp_client_config_set_onreaddir(config.client_config, &fs_readdir);
|
||||
wfp_client_config_set_onopen(config.client_config, &fs_open);
|
||||
wfp_client_config_set_onread(config.client_config, &fs_read);
|
||||
|
||||
struct wfp_client * client = wfp_client_create(config.client_config);
|
||||
wfp_client_connect(client, config.url);
|
||||
|
||||
while (!shutdown_requested)
|
||||
{
|
||||
wfp_client_service(client, SERVICE_TIMEOUT);
|
||||
}
|
||||
|
||||
wfp_client_dispose(client);
|
||||
}
|
||||
|
||||
if (config.show_help)
|
||||
{
|
||||
show_help();
|
||||
}
|
||||
|
||||
free(config.url);
|
||||
wfp_client_config_dispose(config.client_config);
|
||||
return result;
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "webfuse_provider.h"
|
||||
|
||||
#define SERVICE_TIMEOUT (1 * 1000)
|
||||
|
||||
struct args
|
||||
{
|
||||
char const * url;
|
||||
bool show_help;
|
||||
};
|
||||
|
||||
static int
|
||||
parse_args(
|
||||
struct args * args,
|
||||
int argc,
|
||||
char * argv[])
|
||||
{
|
||||
int result = EXIT_FAILURE;
|
||||
args->show_help = true;
|
||||
args->url = NULL;
|
||||
|
||||
if (2 == argc)
|
||||
{
|
||||
result = EXIT_SUCCESS;
|
||||
|
||||
char const * url = argv[1];
|
||||
if ((0 != strcmp(url, "-h")) && (0 != strcmp(url, "--help")))
|
||||
{
|
||||
args->show_help = false;
|
||||
args->url = url;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "error: missing argument\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static volatile bool shutdown_requested = false;
|
||||
|
||||
static void on_interrupt(int signal_id)
|
||||
{
|
||||
(void) signal_id;
|
||||
shutdown_requested = true;
|
||||
}
|
||||
|
||||
static void print_usage()
|
||||
{
|
||||
printf(
|
||||
"static-filesystem-provider Copyright (c) 2019, webfuse authors <https://github.com/falk-werner/webfuse>\n"
|
||||
"Example of webfuse static filesystem provider\n"
|
||||
"\n"
|
||||
"Usage: static-filesystem-provider <url>\n"
|
||||
"\n"
|
||||
"Arguments:\n"
|
||||
"\t<url> URL of webfuse server (required)\n"
|
||||
"\t-h, --help prints this message\n"
|
||||
"\n"
|
||||
"Example:\n"
|
||||
"\tstatic-filesystem-provider ws://localhost:8080/\n"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
signal(SIGINT, &on_interrupt);
|
||||
|
||||
struct args args;
|
||||
int result = parse_args(&args, argc, argv);
|
||||
if ((EXIT_SUCCESS == result) && (!args.show_help))
|
||||
{
|
||||
struct wfp_client_config * config = wfp_client_config_create();
|
||||
|
||||
struct wfp_static_filesystem * fs = wfp_static_filesystem_create(config);
|
||||
wfp_static_filesystem_add_text(fs, "brummni/hello_world.txt", 0444, "Hello, World!\n");
|
||||
wfp_static_filesystem_add_text(fs, "brummni/hello_bob.txt", 0444, "Hello, Bob!\n");
|
||||
wfp_static_filesystem_add_text(fs, "brummni/hello_bob.txt", 0444, "Hello, Alice!\n");
|
||||
wfp_static_filesystem_add_text(fs, "bla/hello_world.txt", 0444, "Hello, World!\n");
|
||||
wfp_static_filesystem_add_text(fs, "foo.txt", 0444, "foo\n");
|
||||
wfp_static_filesystem_add_text(fs, "bar.txt", 0444, "bar\n");
|
||||
|
||||
struct wfp_client * client = wfp_client_create(config);
|
||||
wfp_client_connect(client, args.url);
|
||||
|
||||
while (!shutdown_requested)
|
||||
{
|
||||
wfp_client_service(client, SERVICE_TIMEOUT);
|
||||
}
|
||||
|
||||
wfp_client_dispose(client);
|
||||
wfp_static_filesystem_dispose(fs);
|
||||
wfp_client_config_dispose(config);
|
||||
}
|
||||
|
||||
if (args.show_help)
|
||||
{
|
||||
print_usage();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
Loading…
Reference in New Issue
Block a user