Compare commits

...

6 Commits

Author SHA1 Message Date
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
4 changed files with 153 additions and 57 deletions

View File

@ -13,7 +13,7 @@ add_executable(${PROJECT_NAME}
target_compile_options(${PROJECT_NAME} PRIVATE -Wall)
target_compile_definitions(${PROJECT_NAME} PRIVATE _FILE_OFFSET_BITS=64)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR}/cmake)
find_package(web 0.2.0)
find_package(web 0.3.0)
if(WEB_FOUND)
find_package(dynstr 0.1.0)

2
libweb

@ -1 +1 @@
Subproject commit 6ceae16a20175edb77fb2ffab0d3d6648d011221
Subproject commit b4930f72bb9026c5a0871f4fa4cabe20cb0e6a9f

74
main.c
View File

@ -1057,23 +1057,23 @@ end:
static int move_file(const char *const old, const char *const new)
{
int ret = -1;
FILE *const f = fopen(old, "rb");
const int fd = open(new, O_WRONLY | O_CREAT, 0600);
const int fd_old = open(old, O_RDONLY),
fd_new = open(new, O_WRONLY | O_CREAT, 0600);
struct stat sb;
if (!f)
if (fd_old < 0)
{
fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
fprintf(stderr, "%s: open(2) fd_old: %s\n", __func__, strerror(errno));
goto end;
}
else if (fd < 0)
else if (fd_new < 0)
{
fprintf(stderr, "%s: open(2): %s\n", __func__, strerror(errno));
goto end;
}
else if (stat(old, &sb))
else if (fstat(fd_old, &sb))
{
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
fprintf(stderr, "%s: fstat(2): %s\n", __func__, strerror(errno));
goto end;
}
@ -1082,24 +1082,30 @@ static int move_file(const char *const old, const char *const new)
char buf[BUFSIZ];
const off_t left = sb.st_size - i;
const size_t rem = left > sizeof buf ? sizeof buf : left;
ssize_t w;
const ssize_t r = read(fd_old, buf, rem);
if (!fread(buf, rem, 1, f))
if (r < 0)
{
fprintf(stderr, "%s: fread(3) failed, feof=%d, ferror=%d\n",
__func__, feof(f), ferror(f));
fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));
goto end;
}
else if ((w = write(fd, buf, rem)) < 0)
size_t wrem = r;
const void *p = buf;
while (wrem)
{
fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno));
goto end;
}
else if (w != rem)
{
fprintf(stderr, "%s: write(2): expected to write %zu bytes, "
"only %ju written\n", __func__, rem, (intmax_t)w);
goto end;
const ssize_t w = write(fd_new, p, wrem);
if (w < 0)
{
fprintf(stderr, "%s: write(2): %s\n",
__func__, strerror(errno));
goto end;
}
p = (const char *)p + w;
wrem -= w;
}
i += rem;
@ -1108,15 +1114,15 @@ static int move_file(const char *const old, const char *const new)
ret = 0;
end:
if (fd >= 0 && close(fd))
if (fd_old >= 0 && close(fd_old))
{
fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));
fprintf(stderr, "%s: close(2) fd_old: %s\n", __func__, strerror(errno));
ret = -1;
}
if (f && fclose(f))
if (fd_new >= 0 && close(fd_new))
{
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
fprintf(stderr, "%s: close(2) fd_new: %s\n", __func__, strerror(errno));
ret = -1;
}
@ -1350,6 +1356,7 @@ static int createdir(const struct http_payload *const p,
struct dynstr d, userd;
struct form *forms = NULL;
size_t n = 0;
char *encurl = NULL;
dynstr_init(&d);
dynstr_init(&userd);
@ -1376,7 +1383,7 @@ static int createdir(const struct http_payload *const p,
goto end;
}
char *name = NULL, *dir = NULL;
const char *name = NULL, *dir = NULL;
for (size_t i = 0; i < n; i++)
{
@ -1463,7 +1470,12 @@ static int createdir(const struct http_payload *const p,
.status = HTTP_STATUS_SEE_OTHER
};
if (http_response_add_header(r, "Location", userd.str))
if (!(encurl = http_encode_url(userd.str)))
{
fprintf(stderr, "%s: http_encode_url failed\n", __func__);
goto end;
}
else if (http_response_add_header(r, "Location", encurl))
{
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
goto end;
@ -1476,6 +1488,7 @@ end:
forms_free(forms, n);
dynstr_free(&userd);
dynstr_free(&d);
free(encurl);
return ret;
}
@ -2090,7 +2103,14 @@ int main(int argc, char *argv[])
{
.length = check_length,
.tmpdir = tmpdir,
.user = a
.user = a,
.post =
{
/* Arbitrary limit. */
.max_files = 10000,
/* File upload only requires one pair. */
.max_pairs = 1
}
};
unsigned short outport;

132
page.c
View File

@ -108,6 +108,7 @@ static int prepare_name(struct html_node *const n, struct stat *const sb,
struct html_node *a;
struct dynstr d, dname;
const char *const sep = S_ISDIR(sb->st_mode) ? "/" : "";
char *encurl = NULL;
dynstr_init(&d);
dynstr_init(&dname);
@ -122,7 +123,12 @@ static int prepare_name(struct html_node *const n, struct stat *const sb,
fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
goto end;
}
else if (html_node_add_attr(a, "href", d.str))
else if (!(encurl = http_encode_url(d.str)))
{
fprintf(stderr, "%s: http_encode_url failed\n", __func__);
goto end;
}
else if (html_node_add_attr(a, "href", encurl))
{
fprintf(stderr, "%s: html_node_add_attr href failed\n", __func__);
goto end;
@ -143,6 +149,7 @@ static int prepare_name(struct html_node *const n, struct stat *const sb,
end:
dynstr_free(&d);
dynstr_free(&dname);
free(encurl);
return ret;
}
@ -303,6 +310,7 @@ static int prepare_preview(struct html_node *const n,
const struct stat *const sb, const char *const dir, const char *const name)
{
int ret = -1;
char *encurl = NULL;
struct html_node *a;
struct dynstr d;
@ -315,9 +323,22 @@ static int prepare_preview(struct html_node *const n,
fprintf(stderr, "%s: html_node_add_child form failed\n", __func__);
goto end;
}
else if (dynstr_append(&d, "%s%s?preview=1", dir, name))
else if (dynstr_append(&d, "%s%s", dir, name))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
fprintf(stderr, "%s: dynstr_append d failed\n", __func__);
goto end;
}
else if (!(encurl = http_encode_url(d.str)))
{
fprintf(stderr, "%s: http_encode_url failed\n", __func__);
goto end;
}
dynstr_free(&d);
if (dynstr_append(&d, "%s?preview=1", encurl))
{
fprintf(stderr, "%s: dynstr_append encd failed\n", __func__);
goto end;
}
else if (html_node_add_attr(a, "href", d.str))
@ -335,6 +356,7 @@ static int prepare_preview(struct html_node *const n,
end:
dynstr_free(&d);
free(encurl);
return ret;
}
@ -1272,22 +1294,18 @@ end:
}
static int serve_file(struct http_response *const r,
const struct stat *const sb, const char *const res, const bool preview)
const struct stat *const sb, const char *const res, const bool preview,
const int fd, bool *const fdopened)
{
int ret = -1;
FILE *const f = fopen(res, "rb");
FILE *f = NULL;
struct dynstr b, d;
char *bn;
dynstr_init(&b);
dynstr_init(&d);
if (!f)
{
fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
goto end;
}
else if (dynstr_append(&b, "%s", res))
if (dynstr_append(&b, "%s", res))
{
fprintf(stderr, "%s: dynstr_append res failed\n", __func__);
goto end;
@ -1311,6 +1329,14 @@ static int serve_file(struct http_response *const r,
goto end;
}
if (!(f = fdopen(fd, "rb")))
{
fprintf(stderr, "%s: fdopen(3): %s\n", __func__, strerror(errno));
goto end;
}
*fdopened = true;
*r = (const struct http_response)
{
.status = HTTP_STATUS_OK,
@ -1380,28 +1406,63 @@ static bool preview(const struct page_resource *const pr)
int page_resource(const struct page_resource *const pr)
{
int ret = -1;
struct stat sb;
const int fd = open(pr->res, O_RDONLY);
bool fdopened = false;
if (stat(pr->res, &sb))
if (fd < 0)
{
fprintf(stderr, "%s: stat(2) %s: %s\n",
__func__, pr->res, strerror(errno));
if (errno == ENOENT || errno == ENOTDIR)
return page_not_found(pr->r);
ret = page_not_found(pr->r);
else
return -1;
fprintf(stderr, "%s: open(2) %s: %s\n",
__func__, pr->res, strerror(errno));
goto end;
}
else if (fstat(fd, &sb))
{
fprintf(stderr, "%s: fstat(2) %s: %s\n",
__func__, pr->res, strerror(errno));
goto end;
}
const mode_t m = sb.st_mode;
if (S_ISDIR(m))
return list_dir(pr);
{
if (list_dir(pr))
{
fprintf(stderr, "%s: list_dir failed\n", __func__);
goto end;
}
}
else if (S_ISREG(m))
return serve_file(pr->r, &sb, pr->res, preview(pr));
{
if (serve_file(pr->r, &sb, pr->res, preview(pr), fd, &fdopened))
{
fprintf(stderr, "%s: serve_file failed\n", __func__);
goto end;
}
}
else
{
fprintf(stderr, "%s: unexpected st_mode %jo\n", __func__, (intmax_t)m);
goto end;
}
fprintf(stderr, "%s: unexpected st_mode %jd\n", __func__, (intmax_t)m);
return -1;
ret = 0;
end:
if (!fdopened && fd >= 0 && close(fd))
{
fprintf(stderr, "%s: close(2) %s: %s\n",
__func__, pr->res, strerror(errno));
ret = -1;
}
return ret;
}
static char *resolve_link(const char *const res)
@ -1444,18 +1505,26 @@ static char *resolve_link(const char *const res)
int page_public(struct http_response *const r, const char *const res)
{
int ret = -1;
const int fd = open(res, O_RDONLY);
struct stat sb;
char *path = NULL;
bool fdopened = false;
if (stat(res, &sb))
if (fd < 0)
{
fprintf(stderr, "%s: stat(2) %s: %s\n",
__func__, res, strerror(errno));
if (errno == ENOENT)
return page_not_found(r);
ret = page_not_found(r);
else
goto end;
fprintf(stderr, "%s: stat(2) %s: %s\n",
__func__, res, strerror(errno));
goto end;
}
else if (fstat(fd, &sb))
{
fprintf(stderr, "%s: fstat(2) %s: %s\n",
__func__, res, strerror(errno));
goto end;
}
const mode_t m = sb.st_mode;
@ -1470,7 +1539,7 @@ int page_public(struct http_response *const r, const char *const res)
fprintf(stderr, "%s: resolve_link failed\n", __func__);
goto end;
}
else if (serve_file(r, &sb, path, false))
else if (serve_file(r, &sb, path, false, fd, &fdopened))
{
fprintf(stderr, "%s: serve_file failed\n", __func__);
goto end;
@ -1479,6 +1548,13 @@ int page_public(struct http_response *const r, const char *const res)
ret = 0;
end:
if (!fdopened && fd >= 0 && close(fd))
{
fprintf(stderr, "%s: close(2) %s: %s\n",
__func__, res, strerror(errno));
ret = -1;
}
free(path);
return ret;
}