*
*/
-static char *make_etag(struct stat *stat_buf);
-static char *make_disk_path(char *user, char *path, char **storage_root);
+static char *make_disk_path(char *dom_user, char *path, gss_buffer_t authuser, char **storage_root);
static evhtp_res serve_directory(evhtp_request_t *request, char *disk_path,
struct stat *stat_buf);
static evhtp_res serve_file_head(evhtp_request_t *request_t, char *disk_path,
struct stat *stat_buf,const char *mime_type);
static evhtp_res serve_file(evhtp_request_t *request, const char *disk_path,
struct stat *stat_buf);
-static evhtp_res handle_get_or_head(evhtp_request_t *request, int include_body);
-static int content_type_to_xattr(int fd, const char *content_type);
-static int compare_version(struct stat *stat_buf, const char *expected);
+static evhtp_res handle_get_or_head(evhtp_request_t *request, gss_buffer_t authuser, int include_body);
-evhtp_res storage_handle_head(evhtp_request_t *request) {
- return handle_get_or_head(request, 0);
+evhtp_res storage_handle_head(evhtp_request_t *request, gss_buffer_t authuser) {
+ if(RS_EXPERIMENTAL) {
+ return handle_get_or_head(request, authuser, 0);
+ } else {
+ return EVHTP_RES_METHNALLOWED;
+ }
}
-evhtp_res storage_handle_get(evhtp_request_t *request) {
+evhtp_res storage_handle_get(evhtp_request_t *request, gss_buffer_t authuser) {
log_debug("storage_handle_get()");
- return handle_get_or_head(request, 1);
+ return handle_get_or_head(request, authuser, 1);
}
-evhtp_res storage_handle_put(evhtp_request_t *request) {
+evhtp_res storage_handle_put(evhtp_request_t *request, gss_buffer_t authuser) {
log_debug("HANDLE PUT");
if(request->uri->path->file == NULL) {
}
char *storage_root = NULL;
- char *disk_path = make_disk_path(request->uri->path->match_start,
- request->uri->path->match_end,
+ char *disk_path = make_disk_path(REQUEST_GET_USER(request),
+ REQUEST_GET_PATH(request),
+ authuser,
&storage_root);
if(disk_path == NULL) {
- return 500;
+ return EVHTP_RES_SERVERR;
}
// check if file exists (needed for preconditions and response code)
// current version.
evhtp_header_t *if_match = evhtp_headers_find_header(request->headers_in, "If-Match");
- if(if_match && ((!exists) || compare_version(&stat_buf, if_match->val) != 0)) {
+ if(if_match && ((!exists) || strcmp(get_etag(disk_path), if_match->val) != 0)) {
return 412;
}
} while(0);
+#if 0
+ // look up uid and gid of current user, so we can chown() correctly.
+ uid_t uid;
+ gid_t gid;
+ do {
+ char *bufptr = NULL;
+ struct passwd *user_entry = user_get_entry(REQUEST_GET_USER(request), &bufptr);
+ if(user_entry > 0) {
+ uid = user_entry->pw_uid;
+ gid = user_entry->pw_gid;
+ free(user_entry);
+ free(bufptr);
+ } else {
+ return EVHTP_RES_SERVERR;
+ }
+ } while(0);
+#endif
+
// create parent directories
do {
- char *path_copy = strdup(request->uri->path->match_end);
+ char *path_copy = strdup(REQUEST_GET_PATH(request));
if(path_copy == NULL) {
log_error("strdup() failed: %s", strerror(errno));
free(disk_path);
free(storage_root);
- return 500;
+ return EVHTP_RES_SERVERR;
}
char *dir_path = dirname(path_copy);
+ if(strcmp(dir_path, ".") == 0) { // PUT to file below root directory
+ continue;
+ }
char *saveptr = NULL;
char *dir_name;
int dirfd = open(storage_root, O_RDONLY), prevfd;
free(disk_path);
free(path_copy);
free(storage_root);
- return 500;
+ return EVHTP_RES_SERVERR;
}
struct stat dir_stat;
+ log_debug("strtok_r(\"%s\", ...), (dir_path: %p, saveptr: %p)", dir_path, dir_path, saveptr);
for(dir_name = strtok_r(dir_path, "/", &saveptr);
dir_name != NULL;
dir_name = strtok_r(NULL, "/", &saveptr)) {
free(disk_path);
free(path_copy);
free(storage_root);
- return 500;
+ return EVHTP_RES_SERVERR;
}
+
+#if 0
+ if(fchownat(dirfd, dir_name, uid, gid, AT_SYMLINK_NOFOLLOW) != 0) {
+ log_warn("failed to chown() newly created directory: %s", strerror(errno));
+ }
+#endif
}
prevfd = dirfd;
dirfd = openat(prevfd, dir_name, O_RDONLY);
free(disk_path);
free(path_copy);
free(storage_root);
- return 500;
+ return EVHTP_RES_SERVERR;
}
}
if(fd == -1) {
log_error("open() failed to open file \"%s\": %s", disk_path, strerror(errno));
free(disk_path);
- return 500;
+ return EVHTP_RES_SERVERR;
+ }
+
+ if(! exists) {
+#if 0
+ if(fchown(fd, uid, gid) != 0) {
+ log_warn("Failed to chown() newly created file: %s", strerror(errno));
+ }
+#endif
}
// write buffered data
}
// remember content type in extended attributes
- if(content_type_to_xattr(fd, content_type) != 0) {
+ if(content_type_to_xattr(disk_path, content_type) != 0) {
log_error("Setting xattr for content type failed. Ignoring.");
}
if(stat(disk_path, &stat_buf) != 0) {
log_error("failed to stat() file after writing: %s", strerror(errno));
free(disk_path);
- return 500;
+ return EVHTP_RES_SERVERR;
}
- char *etag_string = make_etag(&stat_buf);
+ char *etag_string = get_etag(disk_path);
ADD_RESP_HEADER_CP(request, "Content-Type", content_type);
ADD_RESP_HEADER_CP(request, "ETag", etag_string);
return exists ? EVHTP_RES_OK : EVHTP_RES_CREATED;
}
-evhtp_res storage_handle_delete(evhtp_request_t *request) {
+evhtp_res storage_handle_delete(evhtp_request_t *request, gss_buffer_t authuser) {
if(request->uri->path->file == NULL) {
// DELETE to directories aren't allowed
}
char *storage_root = NULL;
- char *disk_path = make_disk_path(request->uri->path->match_start,
- request->uri->path->match_end,
+ char *disk_path = make_disk_path(REQUEST_GET_USER(request),
+ REQUEST_GET_PATH(request),
+ authuser,
&storage_root);
if(disk_path == NULL) {
- return 500;
+ return EVHTP_RES_SERVERR;
}
struct stat stat_buf;
return 400;
}
+ char *etag_string = get_etag(disk_path);
+
+ evhtp_header_t *if_match = evhtp_headers_find_header(request->headers_in, "If-Match");
+ if(if_match && (strcmp(etag_string, if_match->val) != 0)) {
+ return 412;
+ }
+
+ ADD_RESP_HEADER_CP(request, "ETag", etag_string);
+
// file exists, delete it.
if(unlink(disk_path) == -1) {
log_error("unlink() failed: %s", strerror(errno));
- return 500;
+ return EVHTP_RES_SERVERR;
}
/*
* remove empty parents
*/
- char *path_copy = strdup(request->uri->path->match_end);
+ char *path_copy = strdup(REQUEST_GET_PATH(request));
if(path_copy == NULL) {
log_error("strdup() failed to copy path: %s", strerror(errno));
free(disk_path);
- return 500;
+ return EVHTP_RES_SERVERR;
}
char *dir_path;
int rootdirfd = open(storage_root, O_RDONLY);
log_error("failed to open() storage root: %s", strerror(errno));
free(path_copy);
free(disk_path);
- return 500;
+ return EVHTP_RES_SERVERR;
}
int result;
// skip leading slash
log_error("unlinkat() failed to remove parent directory: %s", strerror(errno));
free(path_copy);
free(disk_path);
- return 500;
+ return EVHTP_RES_SERVERR;
}
}
}
close(rootdirfd);
free(path_copy);
} else {
- // file doesn't exist, ignore it.
+ // file doesn't exist, return 404.
+ return 404;
}
free(storage_root);
return 200;
}
-static char *make_etag(struct stat *stat_buf) {
- char *etag = malloc(21);
- if(etag == NULL) {
- log_error("malloc() failed: %s", strerror(errno));
- return NULL;
- }
- snprintf(etag, 20, "%lld", ((long long int)stat_buf->st_mtime) * 1000);
- return etag;
-}
-
size_t json_buf_writer(char *buf, size_t count, void *arg) {
return evbuffer_add((struct evbuffer*)arg, buf, count);
}
DIR *dir = opendir(disk_path);
if(dir == NULL) {
log_error("opendir() failed: %s", strerror(errno));
- return 500;
+ return EVHTP_RES_SERVERR;
}
struct dirent *entryp = malloc(offsetof(struct dirent, d_name) +
pathconf(disk_path, _PC_NAME_MAX) + 1);
if(entryp == NULL) {
log_error("malloc() failed while creating directory pointer: %s",
strerror(errno));
- return 500;
+ return EVHTP_RES_SERVERR;
}
struct json *json = new_json(json_buf_writer, buf);
char key_string[entry_len + 2];
sprintf(key_string, "%s%s", entryp->d_name,
S_ISDIR(file_stat_buf.st_mode) ? "/": "");
- char *val_string = make_etag(&file_stat_buf);
+ char *val_string = get_etag(full_path);
json_write_key_val(json, key_string, val_string);
free_json(json);
- char *etag = make_etag(stat_buf);
+ char *etag = get_etag(disk_path);
if(etag == NULL) {
- log_error("make_etag() failed");
+ log_error("get_etag() failed");
free(entryp);
closedir(dir);
- return 500;
+ return EVHTP_RES_SERVERR;
}
ADD_RESP_HEADER(request, "Content-Type", "application/json; charset=UTF-8");
ADD_RESP_HEADER_CP(request, "ETag", etag);
- evhtp_send_reply(request, EVHTP_RES_OK);
free(etag);
free(entryp);
closedir(dir);
- return 0;
+ return EVHTP_RES_OK;
}
-static char *get_xattr(const char *path, const char *key, int maxlen) {
- int len = 32;
- char *value = malloc(32);
- for(value = malloc(len);len<=maxlen;value = realloc(value, len+=16)) {
- if(value == NULL) {
- log_error("malloc() / realloc() failed: %s", strerror(errno));
- return NULL;
+static evhtp_res serve_file_head(evhtp_request_t *request, char *disk_path, struct stat *stat_buf, const char *mime_type) {
+
+ log_debug("serve file head");
+
+ if(request->uri->path->file == NULL) {
+ log_debug("HEAD dir requested");
+ // directory was requested
+ if(! S_ISDIR(stat_buf->st_mode)) {
+ log_debug("HEAD file found");
+ // but is actually a file
+ return EVHTP_RES_NOTFOUND;
}
- int actual_len = getxattr(path, key, value, len);
- if(actual_len > 0) {
- return value;
- } else {
- if(errno == ERANGE) {
- // buffer too small.
- continue;
- } else if(errno == ENOATTR) {
- // attribute not set
- free(value);
- return NULL;
- } else if(errno == ENOTSUP) {
- // xattr not supported
- log_error("File system doesn't support extended attributes! You may want to use another one.");
- free(value);
- return NULL;
- } else {
- log_error("Unexpected error while getting %s attribute: %s", key, strerror(errno));
- free(value);
- return NULL;
- }
+ log_debug("HEAD directory found");
+ } else {
+ log_debug("HEAD file requested");
+ // file was requested
+ if(S_ISDIR(stat_buf->st_mode)) {
+ log_debug("HEAD directory found");
+ // but is actually a directory
+ return EVHTP_RES_NOTFOUND;
}
+ log_debug("HEAD file found");
}
- log_error("%s attribute seems to be longer than %d bytes. That is simply unreasonable.", key, maxlen);
- free(value);
- return NULL;
-}
-
-static char *content_type_from_xattr(const char *path) {
- char *mime_type = get_xattr(path, "user.mime_type", 128);
- if(mime_type == NULL) {
- return NULL;
- }
- char *charset = get_xattr(path, "user.charset", 64);
- if(charset == NULL) {
- return mime_type;
- }
- int mt_len = strlen(mime_type);
- char *content_type = realloc(mime_type, mt_len + strlen(charset) + 10 + 1);
- if(content_type == NULL) {
- log_error("realloc() failed: %s", strerror(errno));
- free(charset);
- return mime_type;
+
+ char *etag_string = get_etag(disk_path);
+ if(etag_string == NULL) {
+ log_error("get_etag() failed");
+ return EVHTP_RES_SERVERR;
}
- sprintf(content_type + mt_len, "; charset=%s", charset);
- free(charset);
- return content_type;
-}
-static int content_type_to_xattr(int fd, const char *content_type) {
- char *content_type_copy = strdup(content_type), *saveptr = NULL;
- if(content_type_copy == NULL) {
- log_error("strdup() failed: %s", strerror(errno));
- return -1;
- }
- char *mime_type = strtok_r(content_type_copy, ";", &saveptr);
- log_debug("extracted mime type: %s", mime_type);
- char *rest = strtok_r(NULL, "", &saveptr);
- char *charset_begin = NULL, *charset = NULL;
- if(rest) {
- charset_begin = strstr(rest, "charset=");
- if(charset_begin) {
- charset = charset_begin + 8;
- log_debug("extracted charset: %s", charset);
+ evhtp_header_t *if_none_match_header = evhtp_headers_find_header(request->headers_in, "If-None-Match");
+ if(if_none_match_header) {
+ // FIXME: support multiple comma-separated ETags in If-None-Match header
+ if(strcmp(if_none_match_header->val, etag_string) == 0) {
+ free(etag_string);
+ return EVHTP_RES_NOTMOD;
}
}
- if(charset == NULL) {
- // FIXME: should this rather be binary or us-ascii?
- charset = "UTF-8";
- log_debug("guessed charset: %s", charset);
- }
- if(fsetxattr(fd, "user.mime_type", mime_type, strlen(mime_type) + 1, 0) != 0) {
- log_error("fsetxattr() failed: %s", strerror(errno));
- free(content_type_copy);
- return -1;
- }
- if(fsetxattr(fd, "user.charset", charset, strlen(charset) + 1, 0) != 0) {
- log_error("fsetxattr() failed: %s", strerror(errno));
- free(content_type_copy);
- return -1;
- }
- free(content_type_copy);
- return 0;
-}
+ char *length_string = malloc(24);
+ if(length_string == NULL) {
+ log_error("malloc() failed: %s", strerror(errno));
+ free(etag_string);
+ return EVHTP_RES_SERVERR;
+ }
+ snprintf(length_string, 24, "%ld", stat_buf->st_size);
-static evhtp_res serve_file_head(evhtp_request_t *request, char *disk_path, struct stat *stat_buf, const char *mime_type) {
int free_mime_type = 0;
// mime type is either passed in ... (such as for directory listings)
if(mime_type == NULL) {
free_mime_type = 1;
}
}
- char *length_string = malloc(24);
- if(length_string == NULL) {
- log_error("malloc() failed: %s", strerror(errno));
- return 500;
- }
- snprintf(length_string, 24, "%ld", stat_buf->st_size);
- char *etag_string = make_etag(stat_buf);
- if(etag_string == NULL) {
- log_error("make_etag() failed");
- free(length_string);
- return 500;
- }
log_info("setting Content-Type of %s: %s", request->uri->path->full, mime_type);
ADD_RESP_HEADER_CP(request, "Content-Type", mime_type);
int fd = open(disk_path, O_RDONLY | O_NONBLOCK);
if(fd < 0) {
log_error("open() failed: %s", strerror(errno));
- return 500;
+ return EVHTP_RES_SERVERR;
}
while(evbuffer_read(request->buffer_out, fd, 4096) != 0);
- evhtp_send_reply(request, EVHTP_RES_OK);
- return 0;
+ close(fd);
+ return EVHTP_RES_OK;
}
-static char *make_disk_path(char *user, char *path, char **storage_root) {
+static char *make_disk_path(char *dom_user, char *path, gss_buffer_t authuser, char **storage_root) {
// FIXME: use passwd->pwdir instead of /home/{user}/
// calculate maximum length of path
- int pathlen = ( strlen(user) + strlen(path) +
+ int pathlen = ( strlen(dom_user) + strlen(path) +
6 + // "/home/"
1 + // another slash
RS_HOME_SERVE_ROOT_LEN );
char *disk_path = malloc(pathlen + 1);
+ char *xsfile = NULL;
+ FILE *xsf;
+ char principal [1026];
+ bool authorized;
if(disk_path == NULL) {
log_error("malloc() failed: %s", strerror(errno));
return NULL;
}
- if(storage_root) {
- *storage_root = malloc( 7 + RS_HOME_SERVE_ROOT_LEN + strlen(user) + 1);
- if(*storage_root == NULL) {
- log_error("malloc() failed: %s", strerror(errno));
- free(disk_path);
- return NULL;
+ log_debug("Constructing disk_path for dom_user = \"%s\"", dom_user);
+ xsfile = malloc( 7 + RS_HOME_SERVE_ROOT_LEN + strlen(dom_user) + 1 + 17);
+ if(xsfile == NULL) {
+ log_error("malloc() failed: %s", strerror(errno));
+ free(disk_path);
+ return NULL;
+ }
+ sprintf(xsfile, "/home/%s/%s/.k5remotestorage", dom_user, RS_HOME_SERVE_ROOT);
+ log_debug("Access control list = \"%s\"", xsfile);
+ xsf = fopen (xsfile, "r");
+ authorized = false;
+ if (xsf) {
+ while ((!authorized) && fgets (principal, sizeof (principal)-1, xsf)) {
+ int len = strlen (principal);
+ if ((len > 1) && (principal [len-1] == '\n')) {
+ principal [--len] = '\0';
+ }
+ log_debug("Considering acceptable principal \"%s\"", principal);
+ authorized = (len == authuser->length) && (0 == memcmp (principal, authuser->value, len));
}
- sprintf(*storage_root, "/home/%s/%s", user, RS_HOME_SERVE_ROOT);
+ fclose (xsf);
+ } else {
+ log_error ("Failed to open access control list");
+ free(xsfile);
+ free(disk_path);
+ return NULL;
+ }
+ if (!authorized) {
+ log_error ("Access control list does not contain authorized user");
+ free(xsfile);
+ free(disk_path);
+ return NULL;
+ }
+ log_debug ("xsfile = \"%s\"", xsfile);
+ if(storage_root) {
+ // Cut off .k5remotestorage and reuse for *storage_root
+ xsfile [7 + RS_HOME_SERVE_ROOT_LEN + strlen (dom_user)] = '\0';
+ *storage_root = xsfile;
+ log_debug ("storage_root = \"%s\"", storage_root);
+ } else {
+ free (xsfile);
+ xsfile = NULL;
}
// remove all /.. segments
// (we don't try to resolve them, but instead treat them as garbage)
char *pos = NULL;
- while((pos = strstr(path, "/..")) != NULL) {
+ while((pos = strstr(path, "/..")) != NULL) { // FIXME: this would also filter out valid paths like /foo/..bar
int restlen = strlen(pos + 3);
memmove(pos, pos + 3, restlen);
pos[restlen] = 0;
pos[restlen] = 0;
}
// build path
- sprintf(disk_path, "/home/%s/%s%s", user, RS_HOME_SERVE_ROOT, path);
+ sprintf(disk_path, "/home/%s/%s%s", dom_user, RS_HOME_SERVE_ROOT, path);
+ log_debug ("disk_path = \"%s\"", disk_path);
return disk_path;
}
-static evhtp_res handle_get_or_head(evhtp_request_t *request, int include_body) {
+static evhtp_res handle_get_or_head(evhtp_request_t *request, gss_buffer_t authuser, int include_body) {
log_debug("HANDLE GET / HEAD (body: %s)", include_body ? "true" : "false");
- char *storage_root = NULL;
- char *disk_path = make_disk_path(request->uri->path->match_start,
- request->uri->path->match_end,
- &storage_root);
+ char *disk_path = make_disk_path(REQUEST_GET_USER(request),
+ REQUEST_GET_PATH(request),
+ authuser,
+ NULL);
if(disk_path == NULL) {
- return 500;
+ return EVHTP_RES_SERVERR;
}
- free(storage_root);
-
// stat
struct stat stat_buf;
if(stat(disk_path, &stat_buf) != 0) {
- if(errno != ENOENT) {
+ if(errno != ENOENT && errno != ENOTDIR) {
log_error("stat() failed for path \"%s\": %s", disk_path, strerror(errno));
- return EVHTP_SERVERR;
+ return EVHTP_RES_SERVERR;
} else {
- return EVHTP_NOTFOUND;
+ return EVHTP_RES_NOTFOUND;
}
}
// check for directory
if(request->uri->path->file == NULL) {
// directory requested
- if(! S_ISDIR(stat_buf.st_mode)) {
- // not a directory.
- return EVHTP_RES_NOTFOUND;
- }
- // directory found
if(include_body) {
return serve_directory(request, disk_path, &stat_buf);
} else {
}
} else {
// file requested
- if(S_ISDIR(stat_buf.st_mode)) {
- // found, but is a directory
- return EVHTP_RES_OK;
- }
- // file found
- int head_result = serve_file_head(request, disk_path, &stat_buf, NULL);
+ evhtp_res head_result = serve_file_head(request, disk_path, &stat_buf, NULL);
if(head_result != 0) {
return head_result;
}
}
}
}
-
-static int compare_version(struct stat *stat_buf, const char *expected) {
- char version_string[32];
- sprintf(version_string, "%lld", ((long long int) stat_buf->st_mtime) * 1000);
- return strcmp(version_string, expected);
-}