Compare commits

...

88 Commits

Author SHA1 Message Date
Xavier Del Campo Romero 32af8ddd3d
README.md: Fix CMake build instructions
The previous instructions were simply wrong because `cmake ..` would
attempt to configure the project from the parent directory, instead of
the build directory.
2024-05-05 01:07:01 +02:00
Xavier Del Campo Romero b4572c6217
page.c: Do not get filename on previews
This change should provide the same behaviour, but would avoid
unnecessary calls to dynstr_append and basename(3) when a preview is to
be served.
2024-03-01 00:06:03 +01:00
Xavier Del Campo Romero fb8896bccd
README.md: Update dependencies list
- jq is required by usergen.
- Despite being part of a POSIX.1-2008 environment, m4 is not provided
by Debian or Ubuntu by default.
2024-02-24 08:39:25 +01:00
Xavier Del Campo Romero dd29f9096a
usergen: Do not abort on existing directory
Otherwise, it would not be possible to replace user credentials if the
directory already exists.
2024-02-20 21:44:53 +01:00
Xavier Del Campo Romero 8bcf0bf855
main.c: Improve relative path detection
Otherwise, the following resources would be considered valid:

- /user/../test
- /user/./test
- /user/a/.
- /user/a/./test
2024-02-20 21:24:17 +01:00
Xavier Del Campo Romero afc5cf0dfc
main.c: Reject invalid /public/ requests
Otherwise:

- slcl would accept /public/ (i.e., without a file name) as a valid
resource. This would incorrectly map the public/ directory on the
database, making slcl to return -1 because public/ is not a regular
file.

- slcl would accept directory names (e.g.: /public/dir/), which is never
expected since slcl stores all public files into a single directory.
2024-02-20 08:18:11 +01:00
Xavier Del Campo Romero b7f232366c
main.c: Force valid cookie on check_length
Otherwise, a malicious user could send multipart/form-data requests
without a valid cookie.
2024-02-20 00:17:40 +01:00
Xavier Del Campo Romero 6c3bfa270b
page.c: Use open(2) fdopen(3) and fstat(2)
Now, the same file descriptor can be reused for all of the operations
above, instead of calling stat(2) and fopen(3) separately.
2024-02-19 23:35:09 +01:00
Xavier Del Campo Romero 78c8c4dabb
page.c: URL-encode href
Otherwise, files with special characters, such as '%', could not be
downloaded or previewed.
2024-02-19 23:35:09 +01:00
Xavier Del Campo Romero 55008f2f64
main.c: const-qualify name and dir
There was no reason why these should not be const-qualified. It was
probably missed during the implementation.
2024-02-19 23:35:08 +01:00
Xavier Del Campo Romero 1f8aa578a4
main.c: URL-encode created directories
Otherwise, directories with special characters, such as "%", would not
be accessible when performing the redirection.
2024-02-19 23:35:08 +01:00
Xavier Del Campo Romero a578ad6537
main.c: Use fstat(2) on move_file
This allows to reuse the same file descriptor to both open(2) and
fstat(2) the file.
2024-02-19 23:35:08 +01:00
Xavier Del Campo Romero f6b84b765d
Bump libweb to 0.3.0
The following commits fix a couple of security issues on libweb.

Because of afe0681c0b26bb64bad55d7e86770f346cfa043e, slcl had to be
updated to set up its struct http_cfg_post.

commit afe0681c0b26bb64bad55d7e86770f346cfa043e
Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Mon Feb 19 23:00:56 2024 +0100

    Limit maximum multipart/form-data pairs and files

    A malicious user could inject an infinite number of empty files or
    key/value pairs into a request in order to exhaust the device's
    resources.

commit 9d9e0c2979f43297b2ebbf84f14f064f3f9ced0e
Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Mon Feb 19 22:49:09 2024 +0100

    html.c: Avoid half-init objects on html_node_add_attr

    The previous implementation would leave half-initialised objects if one
    of the calls to strdup(3) failed. Now, n->attrs is only modified when
    all previous memory allocations were successful.
2024-02-19 23:35:08 +01:00
Xavier Del Campo Romero 0f889b409e
main.c: Add missing relative path check 2024-02-19 16:59:54 +01:00
Xavier Del Campo Romero c198199a81
CMakeLists.txt: Bump version to 0.2.0 2024-02-12 23:15:15 +01:00
Xavier Del Campo Romero 69c9f975ba
Bump libweb to 0.2.0 2024-02-12 23:15:14 +01:00
Xavier Del Campo Romero 6d6c350479
usergen: Fix password generation
For longer passwords, od(1) might introduce a newline character, causing
printf(1) to interpret its input string incorrectly.
2024-01-26 20:32:28 +01:00
Xavier Del Campo Romero 8ebefdb9ab
Bump libweb
The following commits introduced performance improvements and bugfixes:

Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Sat Jan 20 01:09:18 2024 +0100

    server.c: Fix wrong priority for do_exit

    Under some specific circumstances, poll(2) would return a positive
    integer, but do_exit might had been previously set. This caused libweb
    to ignore SIGTERM, with the potential risk for an endless loop.

Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Sat Jan 20 01:05:05 2024 +0100

    http.c: Solve performance issues on POST uploads

    Profiling showed that reading multipart/form POST uploads byte-by-byte
    was too slow and typically led to maximum CPU usage. Therefore, the
    older approach (as done up to commit 7efc2b3a) was more efficient, even
    if the resulting code was a bit uglier.
2024-01-26 20:31:34 +01:00
Xavier Del Campo Romero 974d263b7e
configure: Avoid file extension conversion 2024-01-26 20:31:18 +01:00
Xavier Del Campo Romero d9d1811f89
configure: Fix typo
slweb was renamed to libweb some time ago:

commit 28ae865e5e
Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Tue Oct 10 23:43:47 2023 +0200

    Apply slweb renaming to libweb
2023-11-24 01:52:52 +01:00
Xavier Del Campo Romero 3062a63e14
.gitignore: Ignore only ./Makefile
Future commits might introduce Makefiles on other directories that
should not be ignored.
2023-11-24 01:52:00 +01:00
Xavier Del Campo Romero f0368253d1
Bump libweb
The following commit should increase performance for slcl:

commit b0accd099fa8c5110d4c3c68830ad6fd810ca3ec
Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Fri Nov 24 00:52:50 2023 +0100

    http.c: Unify read operations

    So far, libweb would perform different read operations depending on its
    state:

    - For HTTP headers or request bodies, one byte at a time was read.
    - For multipart/form-data, up to BUFSIZ bytes at a time were read.

    However, this caused a significant extra number of syscalls for no
    reason and would increase code complexity, specially when parsing
    multiform/form-data boundaries.

    Now, http_read always reads up to BUFSIZ bytes at a time and process
    them on a loop. Apart from reducing code complexity, this should
    increase performance due to the (much) lower number of syscalls
    required.
2023-11-24 01:38:51 +01:00
Xavier Del Campo Romero c322356670
CMakeLists.txt: Fix wrong parameters for project
When VERSION is given, LANGUAGES becomes mandatory.
2023-11-23 00:07:45 +01:00
Xavier Del Campo Romero 1768210ea4
Upgrade to new libweb interface
Recent commits from libweb brought a few breaking changes. The one below
affected slcl, so it had to be updated according to the new interface:

commit 98f5f52461b0c1ab1ee3331722bd32e2db9e1d41
Author: Xavier Del Campo <xavier.delcampo@midokura.com>
Date:   Thu Nov 16 12:23:08 2023 +0100

    Split handler_loop from handler_listen

    Some applications might set up a struct handler object to listen on any
    port i.e., 0, but still need a way to determine which port number was
    eventually selected by the implementation.

    Therefore, handler_listen has been reduced to the server initialization
    bit, whereas the main loop has been split into its own function, namely
    handler_loop.

    Because of these changes, it no longer made sense for libweb to write
    the selected port to standard output, as this is something now
    applications can do on their own.
2023-11-23 00:06:09 +01:00
Xavier Del Campo Romero daffea4660
main.c: Treat non-existing upload dir as non-fatal
When a user attempts to upload a file into a non-existing directory,
slcl would not check whether the directory exists. Then, rename(3) would
fail and slcl would treat this as a fatal error, effectively closing
itself.

Since this is an example of ill-formed user input, it must be treated as
a non-fatal error, and instead slcl should return a bad request page.
2023-11-23 00:01:41 +01:00
Xavier Del Campo Romero 0854bc0030
Bump libweb
More bugfixes related to partial boundary parsing were provided by this
commit:

commit b71a6174e12b4709acaf8bc151938ba12d2a54f6
Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Sun Nov 12 23:31:57 2023 +0100

    http.c: Fix more issues with partial boundaries

    - http_memmem must not check strlen(a) > n because, in case of a partial
    boundary, it would wrongfully return NULL.
    - If one or more characters from a partial boundary are found at the end
    of a buffer, but the next buffer does not start with the rest of the
    boundary, the accumulated boundary must be reset, and then look for a
    new boundary.
2023-11-12 23:36:17 +01:00
Xavier Del Campo Romero 34d00e10af
Bump libweb
Several bugfixes related to partial boundary parsing were provided by
this commit:

commit 7d02b225fe11fb0c7233cd2ea576485ee920f203
Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Sun Nov 12 06:16:26 2023 +0100

    http.c: Fix several issues with partial boundaries

    - Writing to m->boundary[len] did not make any sense, as len is not
    meant to change between calls to read_mf_boundary_byte.
    - For the same reason, memset(3)ing "len + 1" did not make any sense.
    - When a partial boundary is found, http_memmem must still return st.
    - Calling reset_boundary with prev == 0 did not make sense, since that
    case typically means a partial boundary was found on a previous
    iteration, so m->blen must not be reset.
2023-11-12 06:54:56 +01:00
Xavier Del Campo Romero 8dff21942e
libweb: Bump new signature for http_decode_url
The new signature allows callers to distinguish decoding errors from
fatal errors. This is important for slcl to avoid crashing when
ill-formed data is received from a client.
2023-11-12 01:30:43 +01:00
Xavier Del Campo Romero fada861c5f
README.md: Remove wrong comment about /tmp
/tmp is at least defined by POSIX.1-2017 at section 10 (Directory
Structure and Devices).
2023-10-25 22:29:48 +02:00
Xavier Del Campo Romero 4956df1891
Bump libweb to v0.1.0-rc6 2023-10-25 22:13:23 +02:00
Xavier Del Campo Romero fb6105b1b2
CMakeLists.txt: set project VERSION 2023-10-25 22:13:23 +02:00
Xavier Del Campo Romero fe59dacc14
Bump libweb to v0.1.0-rc5 2023-10-25 22:13:23 +02:00
Xavier Del Campo Romero 6fa38f4d83
Replace handwritten Makefile with configure script 2023-10-25 22:13:23 +02:00
Xavier Del Campo Romero b2be8b4658
CMake: Find system libweb or dynstr if available
So far, slcl's build system would always build libweb and dynstr.
However, this is discouraged by distribution packagers.
2023-10-25 13:45:12 +02:00
Xavier Del Campo Romero fc3db39277
usergen: Call mkdir(1) before database swap
No changes must be committed to the database if mkdir(1) fails.
2023-10-19 17:35:39 +02:00
Xavier Del Campo Romero 8b24f8dcbb
usergen: Replace use of non-standard mktemp(1)
Despite common use in several POSIX operating systems, mktemp(1) is not
defined by POSIX.1-2008, nor even POSIX.1-2017. As long as it is not
introduced, m4(1)'s mkstemp can be used with similar effect.
2023-10-19 17:35:38 +02:00
Xavier Del Campo Romero 9376361bcb
main.c: Use BUFSIZ instead of arbitrary value
According to C99 7.19.1p3:

BUFSIZ is a macro that expands to an integer constant expression that is
the size of the buffer used by the setbuf function.

In other words, this means BUFSIZ is the most optimal length for a
buffer that reads a file into memory in chunks using fread(3).
2023-10-19 15:32:22 +02:00
Xavier Del Campo Romero 1da12097cb
CMakeLists.txt: Fix wrong library name 2023-10-14 13:10:37 +02:00
Xavier Del Campo Romero d96e5685ee
auth.c: Fix potential signed integer overflow
For platforms where int is a 16-bit data type, this operation might
overflow and possibly cause either unexpected behaviour and/or a
compiler warning.

Therefore, it is safer to promote each integer constant accordingly.
2023-10-14 13:08:25 +02:00
Xavier Del Campo Romero 472b4ddbf1
Implement HEAD support 2023-10-14 01:08:02 +02:00
Xavier Del Campo Romero 1d2ce3f9c2
List number of files and directories
Sometimes, users want to know how many files and/or directories reside
on a directory. Now, slcl prints such information below the logout form.
2023-10-11 00:08:44 +02:00
Xavier Del Campo Romero 91f1a38d5c
page.c: Force whitespace rendering 2023-10-11 00:08:44 +02:00
Xavier Del Campo Romero 28ae865e5e
Apply slweb renaming to libweb 2023-10-11 00:08:40 +02:00
Xavier Del Campo Romero e3177b549d
Bump slweb 2023-10-02 15:15:19 +02:00
Xavier Del Campo Romero 0cea9b44a6
Bump slweb to v0.1.0-rc1 2023-09-27 22:13:52 +02:00
Xavier Del Campo Romero 2d2f9e4492
man1: Replace LICENSE/AUTHORS with COPYRIGHT
LICENSE is not copied to the installation prefix, so adding a short
copyright notice instead might be a better reference.
2023-09-27 22:13:52 +02:00
Xavier Del Campo Romero a9d6cdf2e9
CMakeLists.txt: Install targets 2023-09-27 22:13:52 +02:00
Xavier Del Campo Romero f7293744ce
Makefile: add install target 2023-09-27 22:08:23 +02:00
Xavier Del Campo Romero 653924ba87
slcl.1: Remove obsolete TODO
This feature was already implemented by:

commit 0822a982ef
Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Sat Jul 8 00:54:59 2023 +0200

    Implement file/directory removal
2023-09-27 01:48:18 +02:00
Xavier Del Campo Romero b2037fea90
main.c: Refactor calls to handler_add 2023-09-16 01:46:07 +02:00
Xavier Del Campo Romero bec528a979
usergen: Remove dependency against sha256sum(1)
sha256sum(1) is a GNU utility that might not be available under some
POSIX systems. Since OpenSSL is already a dependency, it makes sense to
reuse it to generate SHA256 digests.
2023-09-16 01:00:14 +02:00
Xavier Del Campo Romero 18bd0d83be
usergen: Remove dependency against xxd(1)
xxd(1) is closely related to vim(1), might not be available under
some POSIX systems.
2023-09-16 01:00:05 +02:00
Xavier Del Campo Romero d8f683d9ca
usergen: Reject non-numeric, invalid quota 2023-09-16 00:59:46 +02:00
Xavier Del Campo Romero b5327b2f7a
Reduce minimum required major version for OpenSSL
slcl has been successfully tested with OpenSSL 2.0 on an OpenBSD 7.3
host.
2023-09-16 00:29:07 +02:00
Xavier Del Campo Romero 5af036c37c
auth.c: Add missing include
As opposed to other integer constants such as ULLONG_MAX, SIZE_MAX is
defined by stdint.h, not limits.h.
2023-09-15 22:34:07 +02:00
Xavier Del Campo Romero e81d1f6312
main.c: Remove string duplication in get_forms
slweb now assumes application/x-www-form-urlencoded-data as text, so it
now returns a null-terminated string on struct http_post member "data".
This removes the need for slcl to call strdup(3) in order to obtain a
null-terminated string.
2023-09-09 02:34:26 +02:00
Xavier Del Campo Romero fcef3b99b8
Check directory on uploads
For historical reasons, slweb used to check for a name called "dir" on
multipart/form-data POST requests. However, stricly speaking this is
application logic, so it has been now moved from slweb to slcl.

This has resulted in a couple of breaking changes in slweb that had to
be updated on slcl.
2023-09-09 00:50:22 +02:00
Xavier Del Campo Romero 7471da3886
page.c: Update project URL 2023-09-09 00:19:07 +02:00
Xavier Del Campo Romero df873a988c
main.c: Return 1 on null buffer
Malformed POST requests might include no payload data. However, this is
not considered a fatal error, but wrong user input.
2023-09-09 00:15:39 +02:00
Xavier Del Campo Romero 93b146a8a8
Bump slweb
Last commits introduced several minor bugfixes and improvements.
2023-09-07 16:12:55 +02:00
Xavier Del Campo Romero 474de49bad
Add screenshots 2023-08-08 13:05:33 +02:00
Xavier Del Campo Romero a53781a297
Bump slweb
d55b84f68b90fe5c2521724d0c22ebf22e62b0b4 introduces a bugfix:

Author: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Date:   Tue Aug 8 00:32:21 2023 +0200

    html.c: Fix wrong encoding for '>' and '<'
2023-08-08 00:38:52 +02:00
Xavier Del Campo Romero 77ceb994cf
Bump slweb 2023-08-02 13:07:20 +02:00
Xavier Del Campo Romero 0e324e6d77
CMakeLists.txt: Set project language to C
Otherwise, CMake by default tests the system C++ compiler, but this is
not a requirement for slcl.
2023-08-02 13:05:23 +02:00
Xavier Del Campo Romero a88589db6f
Makefile: run the clean target recursively 2023-08-01 02:25:30 +02:00
Xavier Del Campo Romero 08b2cfd2de
Bump slweb 2023-08-01 02:25:30 +02:00
Xavier Del Campo Romero 710852ec71
Makefile: Add FORCE target
When added to targets $(DYNSTR) and $(SLWEB), this would force running
the recursive Makefiles, which might then (or might not) rebuild
targets.
2023-08-01 02:25:30 +02:00
Xavier Del Campo Romero 75f1f223d4
Do some minor rebranding
Despite designed around portability and minimalism, I feel slcl no
longer aligns with the philosophical views from the suckless project.
Therefore, I think it was appropriate to unlink its branding from it.
2023-08-01 02:25:30 +02:00
Xavier Del Campo Romero 7cc5ab1e79
README.md: Inform about -t command line option 2023-07-30 23:41:18 +02:00
Xavier Del Campo Romero 9bcad04de6
Move slweb to new URL 2023-07-28 01:40:44 +02:00
Xavier Del Campo Romero f84cfcfa14
README.md: Add references to slweb
slweb is the HTTP/1.1 server implementation and utilities that slcl
originally implemented, which have now been split into a separate
repository.
2023-07-21 01:40:55 +02:00
Xavier Del Campo Romero f4aa3367f8
Remove dynstr as a submodule
It is now provided by slweb instead.
2023-07-21 01:40:55 +02:00
Xavier Del Campo Romero e49f1da7ae
Adapt to slweb's include paths
slweb puts its header files into its own directory in order to avoid
potential name clashing.
2023-07-21 01:40:55 +02:00
Xavier Del Campo Romero 6e5c091d8f
Adapt build system to slweb
Now, slweb is a library slcl depends on, which includes the HTTP/1.1
server implementation, as well as other utilities.
2023-07-21 01:40:55 +02:00
Xavier Del Campo Romero 7fe639b3ba
Remove files now provided by slweb 2023-07-21 01:40:55 +02:00
Xavier Del Campo Romero 1d4480c0f3
Import slweb
slweb contains the HTTP/1.1 server implementation and surrounding
utilities that are used by slcl. The motivation behind this was to allow
these components to be used by other projects, as well as effectively
making slcl smaller and more modular.
2023-07-21 01:40:51 +02:00
Xavier Del Campo Romero 4eb044e625
Makefile: Allow users to define LDFLAGS
This should allow for easier packaging if extra linker flags are
required.
2023-07-16 03:33:40 +02:00
Xavier Del Campo Romero 38f3f82a77
Limit amount of search results
When a user enters a search term that is too generic, slcl would
generate a long list of search results, where this generation could have
a big impact on the server performance and its available resources.

Therefore, it is reasonable to limit the number of search results to an
arbitrary limit, so that users are forced to enter a more specific
search term in order to achieve more relevant results.
2023-07-11 13:27:49 +02:00
Xavier Del Campo Romero 59e17afe29
cftw: Allow user callback to stop recursive search
So far, cftw would search through all directories and files recursively,
until all objects are processed. However, it is interesting for the user
callback to be able to stop this process under specific circumstances.

Now, cftw will pass a pointer to a bool, initialised to false by
default, that can be optionally assigned to true by the user
callback.

Future commits will make use of this feature. For example, this will be
used to limit the number of search results when a user enters a search
term that is too generic and would otherwise generate a large amount of
search results.
2023-07-11 13:27:49 +02:00
Xavier Del Campo Romero 48b171335c
main.c: Use path_isrel for search terms
Search terms cannot use the same strict rules used for filenames or
directory names, as otherwise examples such as "*folder*/*IMG*" would
not work.
2023-07-11 13:27:49 +02:00
Xavier Del Campo Romero e79e955d93
Allow admins to define their own stylesheet
slcl used to provide a hardcoded stylesheet. However, it would be
desirable for some admins to provide a custom stylesheet without having
to rebuild the application.

Now, slcl creates a default stylesheet, namely style.css, into the
target directory, that can be later modified by admins.

While this might contradict the suckless philosophy a bit, hopefully
some admins might find this new feature useful.
2023-07-11 01:49:12 +02:00
Xavier Del Campo Romero 4236c7fc3a
page.c: Allow add_element to hide checkboxes
Since removing files or directories is currently not an option in
searches, it is better to leave checkboxes out to avoid confusion.
2023-07-09 05:54:56 +02:00
Xavier Del Campo Romero 55f0efb8ab
page.c: Remove back button from searches
- The back button would not return to the previous directory, but to the
user root directory.
- While this could have been solved easily (e.g.: by inserting the
referrer directory into the form), it would have implied extra and
unneeded complexity.
2023-07-09 05:54:56 +02:00
Xavier Del Campo Romero 82c68c4a02
wildcard_cmp.c: Fix out-of-bounds cmp
When the distance between '*' on a wildcard expression was larger than
the string to compare with, this would cause an out-of-bounds read
because `n` was not being limited to the strlen(3) from the input
string.

Example:

- s="c", p="*cc*", casecmp=false

Here, the distance between the first and second '*' is 2 bytes, which is
longer than the input string itself (1 byte, not counting the
terminating null byte '\0').
2023-07-09 05:54:56 +02:00
Xavier Del Campo Romero b5282b2365
main.c: Disallow invalid filenames or directory names
- Relative paths must not be used for filenames or directory names,
such as "..", "." or "dir/..".
- Paths with asterisks ('*') must not be allowed, to avoid confusion
with wildcard expressions.
2023-07-09 05:54:53 +02:00
Xavier Del Campo Romero fbd730754b
http.c: Disallow forbidden filenames during upload
- '.' or '..' must not be used for filenames.
- Filenames must not contain forward slashes ('/').
- Filenames must not contain asterisks ('*') to avoid confusion with
wildcard expressions.
2023-07-09 05:41:43 +02:00
Xavier Del Campo Romero fa8217c511
http.c: Use case-insensitive compare for Content-Disposition
HTTP headers are case-insensitive, so the implementation must accept
Content-Diposition, content-disposition or any other variation.
2023-07-09 04:14:14 +02:00
Xavier Del Campo Romero 0822a982ef
Implement file/directory removal
The following workflow has been implemented:

- A new checkbox for each object inside a directory is shown.
- When one or more objects are selected, the user submits a request
through a HTML5 form.
- Then, slcl will ask the user for confirmation, listing the selected
objects, while reminding the user about the effects.
- The user confirms the selection.
- slcl removes the selected objects. All objects from non-empty
directories are removed, too.
- Finally, slcl redirects the user to the directory the request was
made from.
2023-07-08 03:08:07 +02:00
35 changed files with 1890 additions and 3450 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ build/
slcl
*.o
*.d
./Makefile

6
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "dynstr"]
path = dynstr
url = https://gitea.privatedns.org/xavi92/dynstr
[submodule "libweb"]
path = libweb
url = https://gitea.privatedns.org/xavi/libweb

View File

@ -1,22 +1,36 @@
cmake_minimum_required(VERSION 3.13)
project(slcl)
project(slcl LANGUAGES C VERSION 0.2.0)
add_executable(${PROJECT_NAME}
auth.c
base64.c
cftw.c
handler.c
hex.c
html.c
http.c
jwt.c
main.c
page.c
server.c
wildcard_cmp.c
style.c
)
target_compile_options(${PROJECT_NAME} PRIVATE -Wall)
target_compile_definitions(${PROJECT_NAME} PRIVATE _FILE_OFFSET_BITS=64)
add_subdirectory(dynstr)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR}/cmake)
find_package(web 0.3.0)
if(WEB_FOUND)
find_package(dynstr 0.1.0)
else()
message(STATUS "Using in-tree libweb")
add_subdirectory(libweb)
#dynstr is already provided by libweb.
endif()
find_package(cJSON 1.0 REQUIRED)
find_package(OpenSSL 3.0 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE dynstr cjson OpenSSL::SSL)
find_package(OpenSSL 2.0 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE web dynstr cjson OpenSSL::SSL)
install(TARGETS ${PROJECT_NAME})
install(FILES usergen
TYPE BIN
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
add_subdirectory(doc)

View File

@ -1,37 +0,0 @@
.POSIX:
PROJECT = slcl
O = -Og
CDEFS = -D_FILE_OFFSET_BITS=64 # Required for large file support on 32-bit.
CFLAGS = $(O) $(CDEFS) -g -Wall -Idynstr/include -MD -MF $(@:.o=.d)
LIBS = -lcjson -lssl -lm -lcrypto
LDFLAGS = $(LIBS)
DEPS = $(OBJECTS:.o=.d)
DYNSTR = dynstr/libdynstr.a
DYNSTR_FLAGS = -Ldynstr -ldynstr
OBJECTS = \
auth.o \
base64.o \
cftw.o \
handler.o \
hex.o \
html.o \
http.o \
jwt.o \
main.o \
page.o \
server.o \
wildcard_cmp.o \
all: $(PROJECT)
clean:
rm -f $(OBJECTS) $(DEPS)
$(PROJECT): $(OBJECTS) $(DYNSTR)
$(CC) $(OBJECTS) $(LDFLAGS) $(DYNSTR_FLAGS) -o $@
$(DYNSTR):
+cd dynstr && $(MAKE)
-include $(DEPS)

View File

@ -1,19 +1,26 @@
# slcl, a suckless cloud
# slcl, a simple and lightweight cloud
`slcl` is a simple and fast implementation of a web file server, commonly
known as "cloud storage" or simply "cloud", written in C99.
`slcl` is a simple and lightweight implementation of a web file server,
commonly known as "cloud storage" or simply "cloud", written in C99 plus
POSIX.1-2008 extensions.
## Screenshots
![Screenshot of slcl's login page](doc/login.png)
![Screenshot of an example user directory](doc/user.png)
## Disclaimer
While `slcl` might not share some of the philosophical views from the
[suckless project](https://suckless.org), it still strives towards minimalism,
simplicity and efficiency.
Intentionally, `slcl` does not share some of the philosophical views from the
[suckless project](https://suckless.org). However, it still strives towards
portability, minimalism, simplicity and efficiency.
## Features
- Private access directory with file uploading, with configurable quota.
- Read-only public file sharing.
- Its own, tiny HTTP/1.1-compatible server.
- Uses [`libweb`](https://gitea.privatedns.org/xavi/libweb), a tiny web framework.
- A simple JSON file as the credentials database.
- No JavaScript.
@ -38,11 +45,12 @@ to `slcl`. If required, encryption should be done before uploading e.g.: using
## Requirements
- A POSIX environment.
- OpenSSL >= 3.0.
- OpenSSL >= 2.0.
- cJSON >= 1.7.15.
- [`dynstr`](https://gitea.privatedns.org/xavi92/dynstr)
- [`dynstr`](https://gitea.privatedns.org/xavi/dynstr)
(provided as a `git` submodule by `libweb`).
- [`libweb`](https://gitea.privatedns.org/xavi/libweb)
(provided as a `git` submodule).
- `xxd` (for [`usergen`](usergen) only).
- `jq` (for [`usergen`](usergen) only).
- CMake (optional).
@ -51,13 +59,13 @@ to `slcl`. If required, encryption should be done before uploading e.g.: using
#### Mandatory packages
```sh
sudo apt install build-essential libcjson-dev libssl-dev
sudo apt install build-essential libcjson-dev libssl-dev m4 jq
```
#### Optional packages
```sh
sudo apt install cmake xxd jq
sudo apt install cmake
```
## How to use
@ -66,7 +74,8 @@ sudo apt install cmake xxd jq
Two build environments are provided for `slcl` - feel free to choose any of
them:
- A mostly POSIX-compliant [`Makefile`](Makefile).
- A [`configure`](configure) POSIX shell and mostly POSIX-compliant
[`Makefile`](Makefile).
- A [`CMakeLists.txt`](CMakeLists.txt).
`slcl` can be built using the standard build process:
@ -74,15 +83,15 @@ them:
#### Make
```sh
$ ./configure
$ make
```
#### CMake
```sh
$ mkdir build/
$ cmake ..
$ cmake --build .
$ cmake -B build
$ cmake --build build/
```
### Setting up
@ -170,6 +179,25 @@ command line option. For example:
slcl -p 7822 ~/my-db/
```
`slcl` requires a temporary directory where files uploaded by users are
temporarily stored until moved to the user directory. By default, `slcl`
attempts to retrieve the path to the temporary directory by inspecting the
`TMPDIR` environment variable, and falls back to `/tmp` if undefined.
If a custom temporary directory is required, it can be defined via command
line option `-t`. For example:
```sh
slcl -t ~/my-tmp -p 7822 ~/my-db
```
**Note:** system-level temporary directories such as `/tmp` might reside
on a filesytem different than the one where the database resides. This
would force `slcl` to copy the contents from uploaded files from the
temporary directory to the database, which might be an expensive operation.
Therefore, in order to avoid expensive copies, define a custom temporary
directory that resides on the same filesystem.
## Why this project?
Previously, I had been recommended Nextcloud as an alternative to proprietary
@ -200,7 +228,7 @@ be even available e.g.: phones.
## License
```
slcl, a suckless cloud.
slcl, a simple and lightweight cloud.
Copyright (C) 2023 Xavier Del Campo Romero
This program is free software: you can redistribute it and/or modify

7
auth.c
View File

@ -1,7 +1,7 @@
#include "auth.h"
#include "hex.h"
#include "http.h"
#include "jwt.h"
#include <libweb/http.h>
#include <cjson/cJSON.h>
#include <dynstr.h>
#include <openssl/sha.h>
@ -9,6 +9,7 @@
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
@ -421,7 +422,7 @@ int auth_quota(const struct auth *const a, const char *const user,
*available = true;
*quota = strtoull(qs, &end, 10);
const unsigned long long mul = 1024 * 1024;
const unsigned long long mul = 1024ul * 1024ul;
if (errno || *end != '\0')
{
@ -435,7 +436,7 @@ int auth_quota(const struct auth *const a, const char *const user,
goto end;
}
*quota *= 1024 * 1024;
*quota *= mul;
break;
}
}

2
auth.h
View File

@ -1,7 +1,7 @@
#ifndef AUTH_H
#define AUTH_H
#include "http.h"
#include <libweb/http.h>
#include <stdbool.h>
struct auth *auth_alloc(const char *dir);

20
cftw.c
View File

@ -9,8 +9,8 @@
#include <stdint.h>
#include <string.h>
int cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, void *), void *const user)
static int do_cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, bool *, void *), bool *const done, void *const user)
{
int ret = -1;
DIR *const d = opendir(dirpath);
@ -58,20 +58,20 @@ int cftw(const char *const dirpath, int (*const fn)(const char *,
__func__, path, strerror(errno));
else if (S_ISDIR(sb.st_mode))
{
if ((ret = cftw(d.str, fn, user)))
if ((ret = do_cftw(d.str, fn, done, user)))
;
else if ((ret = fn(d.str, &sb, user)))
else if ((ret = fn(d.str, &sb, done, user)))
;
}
else if (S_ISREG(sb.st_mode))
ret = fn(d.str, &sb, user);
ret = fn(d.str, &sb, done, user);
else
fprintf(stderr, "%s: unexpected st_mode %ju\n",
__func__, (uintmax_t)sb.st_mode);
dynstr_free(&d);
if (ret)
if (ret || *done)
goto end;
}
@ -87,3 +87,11 @@ end:
return ret;
}
int cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, bool *, void *), void *const user)
{
bool done = false;
return do_cftw(dirpath, fn, &done, user);
}

3
cftw.h
View File

@ -1,11 +1,12 @@
#ifndef CFTW_H
#define CFTW_H
#include <stdbool.h>
#include <sys/stat.h>
/* Thread-safe variant of ftw(3) and nftw(3) that allows passing an
* opaque pointer and removes some unneeded parameters. */
int cftw(const char *dirpath, int (*fn)(const char *fpath,
const struct stat *sb, void *user), void *user);
const struct stat *sb, bool *done, void *user), void *user);
#endif /* CFTW_H */

24
cmake/Findweb.cmake Normal file
View File

@ -0,0 +1,24 @@
mark_as_advanced(WEB_LIBRARY WEB_INCLUDE_DIR)
find_library(WEB_LIBRARY NAMES libweb web)
find_path(WEB_INCLUDE_DIR
NAMES
handler.h
html.h
http.h
server.h
wildcard_cmp.h
PATH_SUFFIXES libweb include/libweb)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(web
DEFAULT_MSG WEB_LIBRARY WEB_INCLUDE_DIR)
if(WEB_FOUND)
if(NOT TARGET web)
add_library(web UNKNOWN IMPORTED)
set_target_properties(web PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${WEB_INCLUDE_DIR}"
IMPORTED_LOCATION "${WEB_LIBRARY}")
endif()
endif()

168
configure vendored Executable file
View File

@ -0,0 +1,168 @@
#! /bin/sh
set -e
default_prefix=/usr/local
prefix=$default_prefix
default_CC='c99'
# FILE_OFFSET_BITS=64 is required for large file support on 32-bit platforms.
default_CFLAGS='-O1 -g -D_FILE_OFFSET_BITS=64 -Wall -MD'
default_LDFLAGS="-lcjson -lssl -lm -lcrypto"
CC=${CC:-$default_CC}
CFLAGS=${CFLAGS:-"$default_CFLAGS $default_NPCFLAGS"}
LDFLAGS=${LDFLAGS:-$default_LDFLAGS}
help()
{
cat <<-EOF
$0 [OPTION ...]
--prefix Set installation directory [$default_prefix]
Some influential environment variables:
CC C compiler [$default_CC]
CFLAGS C compiler flags [$default_CFLAGS]
LDFLAGS Link-time flags [$default_LDFLAGS]
EOF
}
while true; do
split_arg=0
if printf "%s" "$1" | grep -e '=' > /dev/null
then
key="$(printf "%s" "$1" | cut -d '=' -f1)"
value="$(printf "%s" "$1" | cut -d '=' -f2)"
split_arg=1
else
key="$1"
value="$2"
fi
case "$key" in
--prefix ) prefix="$value"; shift; test $split_arg -eq 0 && shift ;;
-h | --help ) help; exit 0 ;;
* ) test "$1" != "" && help && exit 1 || break ;;
esac
done
if pkg-config dynstr
then
in_tree_dynstr=0
CFLAGS="$CFLAGS $(pkg-config --cflags dynstr)"
LDFLAGS="$LDFLAGS $(pkg-config --libs dynstr)"
else
echo "Info: dynstr not found. Using in-tree copy" >&2
in_tree_dynstr=1
CFLAGS="$CFLAGS -Ilibweb/dynstr/include"
LDFLAGS="$LDFLAGS -Llibweb/dynstr -ldynstr"
fi
if pkg-config libweb
then
in_tree_libweb=0
CFLAGS="$CFLAGS $(pkg-config --cflags libweb)"
LDFLAGS="$LDFLAGS $(pkg-config --libs libweb)"
else
echo "Info: libweb not found. Using in-tree copy" >&2
in_tree_libweb=1
CFLAGS="$CFLAGS -Ilibweb/include"
LDFLAGS="$LDFLAGS -Llibweb -lweb"
fi
cleanup()
{
rm -f $F
}
F=/tmp/Makefile.slcl
trap cleanup EXIT
cat <<EOF > $F
.POSIX:
CC = $CC
PREFIX = $prefix
DST = $prefix/bin
CFLAGS = $CFLAGS
LDFLAGS = $LDFLAGS
EOF
cat <<"EOF" >> $F
PROJECT = slcl
DEPS = $(OBJECTS:.o=.d)
OBJECTS = \
auth.o \
base64.o \
cftw.o \
hex.o \
jwt.o \
main.o \
page.o \
style.o
all: $(PROJECT)
install: all usergen
mkdir -p $(DST)
cp slcl usergen $(DST)
chmod 0755 $(DST)/slcl
chmod 0755 $(DST)/usergen
+cd doc && $(MAKE) PREFIX=$(PREFIX) install
FORCE:
$(PROJECT): $(OBJECTS)
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
EOF
if [ $in_tree_dynstr -ne 0 ]
then
cat <<"EOF" >> $F
DYNSTR = libweb/dynstr/libdynstr.a
$(PROJECT): $(DYNSTR)
$(DYNSTR): FORCE
+cd libweb/dynstr && $(MAKE) CC=$(CC)
EOF
fi
if [ $in_tree_libweb -ne 0 ]
then
cat <<"EOF" >> $F
LIBWEB = libweb/libweb.a
$(PROJECT): $(LIBWEB)
$(LIBWEB): FORCE
+cd libweb && $(MAKE) CC=$(CC)
EOF
fi
cat <<"EOF" >> $F
clean:
rm -f $(OBJECTS) $(DEPS)
EOF
if [ $in_tree_dynstr -ne 0 ]
then
cat <<"EOF" >> $F
+cd libweb/dynstr && $(MAKE) clean
EOF
fi
if [ $in_tree_libweb -ne 0 ]
then
cat <<"EOF" >> $F
+cd libweb && $(MAKE) clean
EOF
fi
cat <<"EOF" >> $F
distclean: clean
rm Makefile
EOF
cat <<"EOF" >> $F
-include $(DEPS)
EOF
mv $F Makefile

4
doc/CMakeLists.txt Normal file
View File

@ -0,0 +1,4 @@
install(DIRECTORY man1
TYPE MAN
FILES_MATCHING PATTERN "*.1"
)

8
doc/Makefile Normal file
View File

@ -0,0 +1,8 @@
.POSIX:
PREFIX = /usr/local
all:
install: all
+cd man1 && $(MAKE) PREFIX=$(PREFIX) install

BIN
doc/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

16
doc/man1/Makefile Normal file
View File

@ -0,0 +1,16 @@
.POSIX:
PREFIX = /usr/local
DST = $(PREFIX)/share/man/man1
OBJECTS = \
$(DST)/slcl.1 \
$(DST)/usergen.1
all:
install: $(OBJECTS)
$(DST)/%.1: %.1
mkdir -p $(DST)
cp $< $@
chmod 0644 $@

View File

@ -1,7 +1,7 @@
.TH SLCL 1 slcl
.SH NAME
slcl \- a suckless cloud
slcl \- a simple and lightweight cloud
.SH SYNOPSIS
.B slcl
@ -114,14 +114,13 @@ storing one file each, as well as a publicly-shared file by
└── file2.txt
.EE
.SH LICENSE
See the LICENSE file for further reference.
.SH AUTHORS
Written by Xavier Del Campo Romero.
.SH TODO
Allow deleting files and directories from the web interface.
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.SH SEE ALSO
.B usergen(1)

View File

@ -81,11 +81,13 @@ should be updated to something similar to:
}
.EE
.SH LICENSE
See the LICENSE file for further reference.
.SH AUTHORS
Written by Xavier Del Campo Romero.
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.SH SEE ALSO

BIN
doc/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

1
dynstr

@ -1 +0,0 @@
Subproject commit 5c13c9b8385cb2742f342d4d2f64d4e2d21108ba

315
handler.c
View File

@ -1,315 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "handler.h"
#include "http.h"
#include "server.h"
#include "wildcard_cmp.h"
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct handler
{
struct handler_cfg cfg;
struct elem
{
char *url;
enum http_op op;
handler_fn f;
void *user;
} *elem;
struct server *server;
struct client
{
struct handler *h;
struct server_client *c;
struct http_ctx *http;
struct client *next;
} *clients;
size_t n_cfg;
};
static int on_read(void *const buf, const size_t n, void *const user)
{
struct client *const c = user;
return server_read(buf, n, c->c);
}
static int on_write(const void *const buf, const size_t n, void *const user)
{
struct client *const c = user;
return server_write(buf, n, c->c);
}
static int on_payload(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
struct client *const c = user;
struct handler *const h = c->h;
for (size_t i = 0; i < h->n_cfg; i++)
{
const struct elem *const e = &h->elem[i];
if (e->op == p->op && !wildcard_cmp(p->resource, e->url, true))
return e->f(p, r, e->user);
}
fprintf(stderr, "Not found: %s\n", p->resource);
*r = (const struct http_response)
{
.status = HTTP_STATUS_NOT_FOUND
};
return 0;
}
static int on_length(const unsigned long long len,
const struct http_cookie *const c, struct http_response *const r,
void *const user)
{
struct client *const cl = user;
struct handler *const h = cl->h;
if (h->cfg.length)
return h->cfg.length(len, c, r, h->cfg.user);
return 0;
}
static struct client *find_or_alloc_client(struct handler *const h,
struct server_client *const c)
{
for (struct client *cl = h->clients; cl; cl = cl->next)
{
if (cl->c == c)
return cl;
}
struct client *const ret = malloc(sizeof *ret);
if (!ret)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
return NULL;
}
const struct http_cfg cfg =
{
.read = on_read,
.write = on_write,
.payload = on_payload,
.length = on_length,
.user = ret,
.tmpdir = h->cfg.tmpdir
};
*ret = (const struct client)
{
.c = c,
.h = h,
.http = http_alloc(&cfg)
};
if (!ret->http)
{
fprintf(stderr, "%s: http_alloc failed\n", __func__);
return NULL;
}
if (!h->clients)
h->clients = ret;
else
{
for (struct client *c = h->clients; c; c = c->next)
if (!c->next)
{
c->next = ret;
break;
}
}
return ret;
}
static void client_free(struct client *const c)
{
if (c)
http_free(c->http);
free(c);
}
static int remove_client_from_list(struct handler *const h,
struct client *const c)
{
int ret = -1;
if (server_client_close(h->server, c->c))
{
fprintf(stderr, "%s: server_client_close failed\n",
__func__);
goto end;
}
for (struct client *cl = h->clients, *prev = NULL; cl;
prev = cl, cl = cl->next)
{
if (cl == c)
{
if (!prev)
h->clients = c->next;
else
prev->next = cl->next;
break;
}
}
ret = 0;
end:
client_free(c);
return ret;
}
int handler_listen(struct handler *const h, const short port)
{
if (!(h->server = server_init(port)))
{
fprintf(stderr, "%s: server_init failed\n", __func__);
return -1;
}
for (;;)
{
bool exit, io;
struct server_client *const c = server_poll(h->server, &io, &exit);
if (exit)
{
printf("Exiting...\n");
break;
}
else if (!c)
{
fprintf(stderr, "%s: server_poll failed\n", __func__);
return -1;
}
struct client *const cl = find_or_alloc_client(h, c);
if (!cl)
{
fprintf(stderr, "%s: find_or_alloc_client failed\n", __func__);
return -1;
}
else if (io)
{
bool write, close;
const int res = http_update(cl->http, &write, &close);
if (res || close)
{
if (res < 0)
{
fprintf(stderr, "%s: http_update failed\n", __func__);
return -1;
}
else if (remove_client_from_list(h, cl))
{
fprintf(stderr, "%s: remove_client_from_list failed\n",
__func__);
return -1;
}
}
else
server_client_write_pending(cl->c, write);
}
}
return 0;
}
static void free_clients(struct handler *const h)
{
for (struct client *c = h->clients; c;)
{
struct client *const next = c->next;
server_client_close(h->server, c->c);
client_free(c);
c = next;
}
}
void handler_free(struct handler *const h)
{
if (h)
{
for (size_t i = 0; i < h->n_cfg; i++)
free(h->elem[i].url);
free(h->elem);
free_clients(h);
server_close(h->server);
}
free(h);
}
struct handler *handler_alloc(const struct handler_cfg *const cfg)
{
struct handler *const h = malloc(sizeof *h);
if (!h)
{
fprintf(stderr, "%s: malloc(3) handler: %s\n",
__func__, strerror(errno));
return NULL;
}
*h = (const struct handler){.cfg = *cfg};
return h;
}
int handler_add(struct handler *const h, const char *url,
const enum http_op op, const handler_fn f, void *const user)
{
const size_t n = h->n_cfg + 1;
struct elem *const elem = realloc(h->elem, n * sizeof *h->elem);
if (!elem)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return -1;
}
struct elem *const e = &elem[h->n_cfg];
*e = (const struct elem)
{
.url = strdup(url),
.op = op,
.f = f,
.user = user
};
if (!e->url)
{
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
return -1;
}
h->elem = elem;
h->n_cfg = n;
return 0;
}

View File

@ -1,24 +0,0 @@
#ifndef HANDLER_H
#define HANDLER_H
#include "http.h"
#include <stddef.h>
typedef int (*handler_fn)(const struct http_payload *p,
struct http_response *r, void *user);
struct handler_cfg
{
const char *tmpdir;
int (*length)(unsigned long long len, const struct http_cookie *c,
struct http_response *r, void *user);
void *user;
};
struct handler *handler_alloc(const struct handler_cfg *cfg);
void handler_free(struct handler *h);
int handler_add(struct handler *h, const char *url, enum http_op op,
handler_fn f, void *user);
int handler_listen(struct handler *h, short port);
#endif /* HANDLER_H */

301
html.c
View File

@ -1,301 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "html.h"
#include <dynstr.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct html_node
{
struct html_attribute
{
char *attr, *value;
} *attrs;
char *element, *value;
size_t n;
struct html_node *child, *sibling;
};
static char *html_encode(const char *s)
{
struct dynstr d;
dynstr_init(&d);
if (!*s && dynstr_append(&d, ""))
{
fprintf(stderr, "%s: dynstr_append empty failed\n", __func__);
goto failure;
}
while (*s)
{
static const struct esc
{
char c;
const char *str;
} esc[] =
{
{.c = '<', .str = "&gt;"},
{.c = '>', .str = "&lt;"},
{.c = '&', .str = "&amp;"},
{.c = '\"', .str = "&quot;"},
{.c = '\'', .str = "&apos;"}
};
char buf[sizeof "a"] = {0};
const char *str = NULL;
for (size_t i = 0; i < sizeof esc / sizeof *esc; i++)
{
const struct esc *const e = &esc[i];
if (*s == e->c)
{
str = e->str;
break;
}
}
if (!str)
{
*buf = *s;
str = buf;
}
if (dynstr_append(&d, "%s", str))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto failure;
}
s++;
}
return d.str;
failure:
dynstr_free(&d);
return NULL;
}
int html_node_set_value(struct html_node *const n, const char *const val)
{
if (!(n->value = html_encode(val)))
{
fprintf(stderr, "%s: html_encode failed\n", __func__);
return -1;
}
return 0;
}
int html_node_set_value_unescaped(struct html_node *const n,
const char *const val)
{
if (!(n->value = strdup(val)))
{
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
return -1;
}
return 0;
}
int html_node_add_attr(struct html_node *const n, const char *const attr,
const char *const val)
{
const size_t el = n->n + 1;
struct html_attribute *const attrs = realloc(n->attrs,
el * sizeof *n->attrs), *a = NULL;
if (!attrs)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return -1;
}
a = &attrs[n->n];
*a = (const struct html_attribute){0};
if (!(a->attr = strdup(attr))
|| (val && !(a->value = strdup(val))))
{
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
free(a->attr);
free(a->value);
return -1;
}
n->attrs = attrs;
n->n = el;
return 0;
}
void html_node_add_sibling(struct html_node *const n,
struct html_node *const sibling)
{
for (struct html_node *c = n; c; c = c->sibling)
if (!c->sibling)
{
c->sibling = sibling;
break;
}
}
struct html_node *html_node_add_child(struct html_node *const n,
const char *const element)
{
struct html_node *const child = html_node_alloc(element);
if (!child)
return NULL;
else if (n->child)
html_node_add_sibling(n->child, child);
else
n->child = child;
return child;
}
int serialize_node(struct dynstr *const d, const struct html_node *const n,
const unsigned level)
{
for (unsigned i = 0; i < level; i++)
dynstr_append(d, "\t");
dynstr_append_or_ret_nonzero(d, "<%s", n->element);
if (n->n)
dynstr_append_or_ret_nonzero(d, " ");
for (size_t i = 0; i < n->n; i++)
{
const struct html_attribute *const a = &n->attrs[i];
if (a->value)
dynstr_append_or_ret_nonzero(d, "%s=\"%s\"", a->attr, a->value);
else
dynstr_append_or_ret_nonzero(d, "%s", a->attr);
if (i + 1 < n->n)
dynstr_append_or_ret_nonzero(d, " ");
}
if (!n->value && !n->child)
dynstr_append_or_ret_nonzero(d, "/>");
else
{
dynstr_append_or_ret_nonzero(d, ">");
if (n->value)
dynstr_append_or_ret_nonzero(d, "%s", n->value);
if (n->child)
{
dynstr_append_or_ret_nonzero(d, "\n");
if (serialize_node(d, n->child, level + 1))
{
fprintf(stderr, "%s: serialize_node failed\n", __func__);
return -1;
}
for (unsigned i = 0; i < level; i++)
dynstr_append(d, "\t");
}
dynstr_append_or_ret_nonzero(d, "</%s>", n->element);
}
/* TODO: print siblings */
dynstr_append_or_ret_nonzero(d, "\n");
if (n->sibling)
return serialize_node(d, n->sibling, level);
return 0;
}
int html_serialize(const struct html_node *const n, struct dynstr *const d)
{
return serialize_node(d, n, 0);
}
static void html_attribute_free(struct html_attribute *const a)
{
if (a)
{
free(a->attr);
free(a->value);
}
}
void html_node_free(struct html_node *const n)
{
if (n)
{
struct html_node *s = n->sibling;
html_node_free(n->child);
while (s)
{
struct html_node *const next = s->sibling;
html_node_free(s->child);
free(s->element);
free(s->value);
for (size_t i = 0 ; i < s->n; i++)
html_attribute_free(&s->attrs[i]);
free(s->attrs);
free(s);
s = next;
}
free(n->element);
free(n->value);
for (size_t i = 0 ; i < n->n; i++)
html_attribute_free(&n->attrs[i]);
free(n->attrs);
}
free(n);
}
struct html_node *html_node_alloc(const char *const element)
{
struct html_node *const n = malloc(sizeof *n);
if (!n)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto failure;
}
*n = (const struct html_node)
{
.element = strdup(element)
};
if (!n->element)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto failure;
}
return n;
failure:
html_node_free(n);
return NULL;
}

15
html.h
View File

@ -1,15 +0,0 @@
#ifndef HTML_H
#define HTML_H
#include <dynstr.h>
struct html_node *html_node_alloc(const char *element);
void html_node_free(struct html_node *n);
int html_node_set_value(struct html_node *n, const char *val);
int html_node_set_value_unescaped(struct html_node *n, const char *val);
int html_node_add_attr(struct html_node *n, const char *attr, const char *val);
struct html_node *html_node_add_child(struct html_node *n, const char *elem);
void html_node_add_sibling(struct html_node *n, struct html_node *sibling);
int html_serialize(const struct html_node *n, struct dynstr *d);
#endif /* HTML_H */

1956
http.c

File diff suppressed because it is too large Load Diff

106
http.h
View File

@ -1,106 +0,0 @@
#ifndef HTTP_H
#define HTTP_H
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
struct http_payload
{
enum http_op
{
HTTP_OP_GET,
HTTP_OP_POST
} op;
const char *resource;
struct http_cookie
{
const char *field, *value;
} cookie;
union
{
struct http_post
{
bool expect_continue;
const void *data;
size_t n;
const char *dir;
const struct http_post_file
{
const char *tmpname, *filename;
} *files;
} post;
} u;
const struct http_arg
{
char *key, *value;
} *args;
size_t n_args;
};
#define HTTP_STATUSES \
X(CONTINUE, "Continue", 100) \
X(OK, "OK", 200) \
X(SEE_OTHER, "See other", 303) \
X(BAD_REQUEST, "Bad Request", 400) \
X(UNAUTHORIZED, "Unauthorized", 401) \
X(FORBIDDEN, "Forbidden", 403) \
X(NOT_FOUND, "Not found", 404) \
X(PAYLOAD_TOO_LARGE, "Payload too large", 413) \
X(INTERNAL_ERROR, "Internal Server Error", 500)
struct http_response
{
enum http_status
{
#define X(x, y, z) HTTP_STATUS_##x,
HTTP_STATUSES
#undef X
} status;
struct http_header
{
char *header, *value;
} *headers;
union
{
const void *ro;
void *rw;
} buf;
FILE *f;
unsigned long long n;
size_t n_headers;
void (*free)(void *);
};
struct http_cfg
{
int (*read)(void *buf , size_t n, void *user);
int (*write)(const void *buf, size_t n, void *user);
int (*payload)(const struct http_payload *p, struct http_response *r,
void *user);
int (*length)(unsigned long long len, const struct http_cookie *c,
struct http_response *r, void *user);
const char *tmpdir;
void *user;
};
struct http_ctx *http_alloc(const struct http_cfg *cfg);
void http_free(struct http_ctx *h);
/* Positive return value: user input error, negative: fatal error. */
int http_update(struct http_ctx *h, bool *write, bool *close);
int http_response_add_header(struct http_response *r, const char *header,
const char *value);
char *http_cookie_create(const char *key, const char *value);
char *http_encode_url(const char *url);
char *http_decode_url(const char *url, bool spaces);
#endif /* HTTP_H */

1
libweb Submodule

@ -0,0 +1 @@
Subproject commit b4930f72bb9026c5a0871f4fa4cabe20cb0e6a9f

881
main.c

File diff suppressed because it is too large Load Diff

820
page.c

File diff suppressed because it is too large Load Diff

14
page.h
View File

@ -1,7 +1,8 @@
#ifndef PAGE_H
#define PAGE_H
#include "http.h"
#include <libweb/http.h>
#include <stdbool.h>
#include <stddef.h>
struct page_quota
@ -27,18 +28,27 @@ struct page_search
const char *root;
size_t n;
bool limit_exceeded;
};
struct page_rm
{
const char *dir, **items;
size_t n;
};
int page_login(struct http_response *r);
int page_style(struct http_response *r);
int page_style(struct http_response *r, const char *path);
int page_failed_login(struct http_response *r);
int page_forbidden(struct http_response *r);
int page_bad_request(struct http_response *r);
int page_resource(const struct page_resource *r);
int page_head_resource(struct http_response *r, const char *res);
int page_public(struct http_response *r, const char *res);
int page_share(struct http_response *r, const char *path);
int page_quota_exceeded(struct http_response *r, unsigned long long len,
unsigned long long quota);
int page_search(struct http_response *r, const struct page_search *s);
int page_rm(struct http_response *r, const struct page_rm *rm);
#endif /* PAGE_H */

373
server.c
View File

@ -1,373 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "server.h"
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct server
{
int fd;
struct server_client
{
int fd;
bool write;
struct server_client *prev, *next;
} *c;
};
int server_close(struct server *const s)
{
int ret = 0;
if (!s)
return 0;
else if (s->fd >= 0)
ret = close(s->fd);
free(s);
return ret;
}
int server_client_close(struct server *const s, struct server_client *const c)
{
int ret = 0;
for (struct server_client *ref = s->c; ref; ref = ref->next)
{
if (c == ref)
{
struct server_client *const next = ref->next;
if ((ret = close(c->fd)))
{
fprintf(stderr, "%s: close(2): %s\n",
__func__, strerror(errno));
return -1;
}
else if (ref->prev)
ref->prev->next = next;
else
s->c = next;
if (next)
next->prev = ref->prev;
free(ref);
break;
}
}
return ret;
}
int server_read(void *const buf, const size_t n, struct server_client *const c)
{
const ssize_t r = read(c->fd, buf, n);
if (r < 0)
fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));
return r;
}
int server_write(const void *const buf, const size_t n,
struct server_client *const c)
{
const ssize_t w = write(c->fd, buf, n);
if (w < 0)
fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno));
return w;
}
static struct server_client *alloc_client(struct server *const s)
{
struct sockaddr_in addr;
socklen_t sz = sizeof addr;
const int fd = accept(s->fd, (struct sockaddr *)&addr, &sz);
if (fd < 0)
{
fprintf(stderr, "%s: accept(2): %s\n",
__func__, strerror(errno));
return NULL;
}
const int flags = fcntl(fd, F_GETFL);
if (flags < 0)
{
fprintf(stderr, "%s: fcntl(2) F_GETFL: %s\n",
__func__, strerror(errno));
return NULL;
}
else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK))
{
fprintf(stderr, "%s: fcntl(2) F_SETFL: %s\n",
__func__, strerror(errno));
return NULL;
}
struct server_client *const c = malloc(sizeof *c);
if (!c)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
return NULL;
}
*c = (const struct server_client)
{
.fd = fd
};
if (!s->c)
s->c = c;
else
for (struct server_client *ref = s->c; ref; ref = ref->next)
if (!ref->next)
{
ref->next = c;
c->prev = ref;
break;
}
return c;
}
void server_client_write_pending(struct server_client *const c,
const bool write)
{
c->write = write;
}
static volatile sig_atomic_t do_exit;
static void handle_signal(const int signum)
{
switch (signum)
{
case SIGINT:
/* Fall through. */
case SIGTERM:
do_exit = 1;
break;
default:
break;
}
}
static size_t get_clients(const struct server *const s)
{
size_t ret = 0;
for (const struct server_client *c = s->c; c; c = c->next)
ret++;
return ret;
}
struct server_client *server_poll(struct server *const s, bool *const io,
bool *const exit)
{
struct server_client *ret = NULL;
const size_t n_clients = get_clients(s);
const nfds_t n = n_clients + 1;
struct pollfd *const fds = malloc(n * sizeof *fds);
if (!fds)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto end;
}
struct pollfd *const sfd = &fds[0];
*io = *exit = false;
*sfd = (const struct pollfd)
{
.fd = s->fd,
.events = POLLIN
};
for (struct {const struct server_client *c; size_t j;}
_ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++)
{
struct pollfd *const p = &fds[_.j];
const int fd = _.c->fd;
*p = (const struct pollfd)
{
.fd = fd,
.events = POLLIN
};
if (_.c->write)
p->events |= POLLOUT;
}
int res;
again:
res = poll(fds, n, -1);
if (res < 0)
{
if (do_exit)
{
*exit = true;
goto end;
}
switch (errno)
{
case EAGAIN:
/* Fall through. */
case EINTR:
goto again;
default:
fprintf(stderr, "%s: poll(2): %s\n", __func__, strerror(errno));
break;
}
goto end;
}
else if (!res)
{
fprintf(stderr, "%s: poll(2) returned zero\n", __func__);
goto end;
}
else if (sfd->revents)
{
ret = alloc_client(s);
goto end;
}
for (struct {struct server_client *c; size_t j;}
_ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++)
{
const struct pollfd *const p = &fds[_.j];
if (p->revents)
{
*io = true;
ret = _.c;
goto end;
}
}
fprintf(stderr, "%s: unlisted fd\n", __func__);
end:
free(fds);
return ret;
}
static int init_signals(void)
{
struct sigaction sa =
{
.sa_handler = handle_signal,
.sa_flags = SA_RESTART
};
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL))
{
fprintf(stderr, "%s: sigaction(2) SIGINT: %s\n",
__func__, strerror(errno));
return -1;
}
else if (sigaction(SIGTERM, &sa, NULL))
{
fprintf(stderr, "%s: sigaction(2) SIGTERM: %s\n",
__func__, strerror(errno));
return -1;
}
else if (sigaction(SIGPIPE, &sa, NULL))
{
fprintf(stderr, "%s: sigaction(2) SIGPIPE: %s\n",
__func__, strerror(errno));
return -1;
}
return 0;
}
struct server *server_init(const unsigned short port)
{
struct server *const s = malloc(sizeof *s);
if (!s)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto failure;
}
*s = (const struct server)
{
.fd = socket(AF_INET, SOCK_STREAM, 0)
};
if (s->fd < 0)
{
fprintf(stderr, "%s: socket(2): %s\n", __func__, strerror(errno));
goto failure;
}
else if (init_signals())
{
fprintf(stderr, "%s: init_signals failed\n", __func__);
goto failure;
}
const struct sockaddr_in addr =
{
.sin_family = AF_INET,
.sin_port = htons(port)
};
enum {QUEUE_LEN = 10};
if (bind(s->fd, (const struct sockaddr *)&addr, sizeof addr))
{
fprintf(stderr, "%s: bind(2): %s\n", __func__, strerror(errno));
goto failure;
}
else if (listen(s->fd, QUEUE_LEN))
{
fprintf(stderr, "%s: listen(2): %s\n", __func__, strerror(errno));
goto failure;
}
struct sockaddr_in in;
socklen_t sz = sizeof in;
if (getsockname(s->fd, (struct sockaddr *)&in, &sz))
{
fprintf(stderr, "%s: getsockname(2): %s\n", __func__, strerror(errno));
goto failure;
}
printf("Listening on port %hu\n", ntohs(in.sin_port));
return s;
failure:
server_close(s);
return NULL;
}

View File

@ -1,15 +0,0 @@
#ifndef SERVER_H
#define SERVER_H
#include <stdbool.h>
#include <stddef.h>
struct server *server_init(unsigned short port);
struct server_client *server_poll(struct server *s, bool *io, bool *exit);
int server_read(void *buf, size_t n, struct server_client *c);
int server_write(const void *buf, size_t n, struct server_client *c);
int server_close(struct server *s);
int server_client_close(struct server *s, struct server_client *c);
void server_client_write_pending(struct server_client *c, bool write);
#endif /* SERVER_H */

55
style.c Normal file
View File

@ -0,0 +1,55 @@
#include "style.h"
#include <stddef.h>
const char style_default[] =
"body\n"
"{\n"
" font-family: 'Courier New', Courier, monospace;\n"
"}\n"
"td\n"
"{\n"
" font-size: 14px;\n"
"}\n"
"a\n"
"{\n"
" text-decoration: none;\n"
"}\n"
".userform\n"
"{\n"
" padding: 4px;\n"
"}\n"
".loginform\n"
"{\n"
" display: grid;\n"
"}\n"
"form, label, table\n"
"{\n"
" margin: auto;\n"
"}\n"
"div\n"
"{\n"
" align-items: center;\n"
" display: grid;\n"
"}\n"
"input, .abutton\n"
"{\n"
" margin: auto;\n"
" border: 1px solid;\n"
" border-radius: 8px;\n"
"}\n"
"header, footer\n"
"{\n"
" display: flex;\n"
" justify-content: center;\n"
" text-decoration: auto;\n"
"}\n"
"table\n"
"{\n"
" max-width: 50%;\n"
"}\n"
"tr:nth-child(even)\n"
"{\n"
" background-color: lightgray;\n"
"}\n";
const size_t style_default_len = sizeof style_default - 1;

9
style.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef STYLE_H
#define STYLE_H
#include <stddef.h>
extern const char style_default[];
extern const size_t style_default_len;
#endif

32
usergen
View File

@ -7,6 +7,23 @@ usage()
echo "$0 <dir>"
}
to_hex()
{
od -An -t x1 | tr -d ' ' | tr -d '\n'
}
to_bin()
{
sed -e 's,\([0-9a-f]\{2\}\),\\\\\\x\1,g' | xargs printf
}
mktemp_posix()
{
m4 <<EOF
mkstemp(${TMPDIR:-/tmp}/tmp.XXXXXX)
EOF
}
if [ $# != 1 ]; then
usage >&2
exit 1
@ -42,22 +59,23 @@ echo
echo "Quota, in MiB (leave empty for unlimited quota):" >&2
read -r QUOTA
QUOTA="$(printf '%d' "$QUOTA")"
PWD=$(printf '%s' "$PWD" | xxd -p | tr -d '\n')
SALT=$(openssl rand 32 | xxd -p | tr -d '\n')
KEY=$(openssl rand 32 | xxd -p | tr -d '\n')
PWD=$(printf '%s%s' $SALT "$PWD")
PWD=$(printf '%s' "$PWD" | to_hex)
SALT=$(openssl rand -hex 32)
KEY=$(openssl rand -hex 32)
PWD=$(printf '%s%s' "$SALT" "$PWD")
ROUNDS=1000
for i in $(seq $ROUNDS)
do
printf "\r%d/$ROUNDS" $i >&2
PWD=$(printf '%s' "$PWD" | xxd -p -r | sha256sum | cut -d' ' -f1)
PWD=$(printf '%s' "$PWD" | to_bin | openssl sha256 -r | cut -d' ' -f1)
done
echo >&2
TMP=$(mktemp)
TMP=$(mktemp_posix)
cleanup()
{
@ -75,5 +93,5 @@ jq ".users += [
\"quota\": \"$QUOTA\"
}]" "$DB" > $TMP
mkdir -p "$DIR/user/$USER"
mv $TMP "$DB"
mkdir "$DIR/user/$USER"

View File

@ -30,7 +30,8 @@ int wildcard_cmp(const char *s, const char *p, const bool casecmp)
return r;
}
const size_t n = wc - p;
const size_t auxn = wc - p, rem = strlen(s),
n = auxn > rem ? rem : auxn;
if (n)
{