Demonstration release of the principles underpinning krsd.
[krsd] / src / handler / storage.c
index f21bdf1..d3579bd 100644 (file)
  *
  */
 
-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 int serve_file_head(evhtp_request_t *request_t, char *disk_path,
+static evhtp_res serve_file_head(evhtp_request_t *request_t, char *disk_path,
                            struct stat *stat_buf,const char *mime_type);
-static int serve_file(evhtp_request_t *request, const char *disk_path,
+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 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) {
-  return handle_get_or_head(request, 1);
+evhtp_res storage_handle_get(evhtp_request_t *request, gss_buffer_t authuser) {
+  log_debug("storage_handle_get()");
+  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) {
     // PUT to directories aren't allowed
@@ -49,72 +52,135 @@ evhtp_res storage_handle_put(evhtp_request_t *request) {
   }
 
   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;
   }
-  char *path_copy = strdup(request->uri->path->match_end);
-  if(path_copy == NULL) {
-    log_error("strdup() failed: %s", strerror(errno));
-    free(disk_path);
-    free(storage_root);
-    return 500;
-  }
-  char *dir_path = dirname(path_copy);
-  char *saveptr = NULL;
-  char *dir_name;
-  int dirfd = open(storage_root, O_RDONLY), prevfd;
-  if(dirfd == -1) {
-    log_error("failed to open() storage path (\"%s\"): %s", storage_root, strerror(errno));
-    free(disk_path);
-    free(path_copy);
-    free(storage_root);
-    return 500;
-  }
-  struct stat dir_stat;
-  for(dir_name = strtok_r(dir_path, "/", &saveptr);
-      dir_name != NULL;
-      dir_name = strtok_r(NULL, "/", &saveptr)) {
-    if(fstatat(dirfd, dir_name, &dir_stat, 0) == 0) {
-      if(! S_ISDIR(dir_stat.st_mode)) {
-        // exists, but not a directory
-        log_error("Can't PUT to %s, found a non-directory parent.", request->uri->path->full);
-        close(dirfd);
-        free(disk_path);
-        free(path_copy);
-        free(storage_root);
-        return 400;
-      } else {
-        // directory exists
-      }
+
+  // check if file exists (needed for preconditions and response code)
+  struct stat stat_buf;
+  memset(&stat_buf, 0, sizeof(struct stat));
+  int exists = stat(disk_path, &stat_buf) == 0;
+
+  // check preconditions
+  do {
+
+    // PUT and DELETE requests MAY have an 'If-Match' request header [HTTP], and
+    // MUST fail with a 412 response code if that doesn't match the document's
+    // current version.
+
+    evhtp_header_t *if_match = evhtp_headers_find_header(request->headers_in, "If-Match");
+    if(if_match && ((!exists) || strcmp(get_etag(disk_path), if_match->val) != 0)) {
+      return 412;
+    }
+
+    // A PUT request MAY have an 'If-None-Match:*' header [HTTP], in which
+    // case it MUST fail with a 412 response code if the document already
+    // exists.
+
+    evhtp_header_t *if_none_match = evhtp_headers_find_header(request->headers_in, "If-None-Match");
+    if(if_none_match && strcmp(if_none_match->val, "*") == 0 && exists) {
+      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 {
-      if(mkdirat(dirfd, dir_name, S_IRWXU | S_IRWXG) != 0) {
-        log_error("mkdirat() failed: %s", strerror(errno));
-        close(dirfd);
-        free(disk_path);
-        free(path_copy);
-        free(storage_root);
-        return 500;
-      }
+      return EVHTP_RES_SERVERR;
+    }
+  } while(0);
+#endif
+
+  // create parent directories
+  do {
+
+    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 EVHTP_RES_SERVERR;
     }
-    prevfd = dirfd;
-    dirfd = openat(prevfd, dir_name, O_RDONLY);
-    close(prevfd);
+    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;
     if(dirfd == -1) {
-      log_error("failed to openat() next directory (\"%s\"): %s",
-                dir_name, strerror(errno));
+      log_error("failed to open() storage path (\"%s\"): %s", storage_root, strerror(errno));
       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)) {
+      if(fstatat(dirfd, dir_name, &dir_stat, 0) == 0) {
+        if(! S_ISDIR(dir_stat.st_mode)) {
+          // exists, but not a directory
+          log_error("Can't PUT to %s, found a non-directory parent.", request->uri->path->full);
+          close(dirfd);
+          free(disk_path);
+          free(path_copy);
+          free(storage_root);
+          return 400;
+        } else {
+          // directory exists
+        }
+      } else {
+        if(mkdirat(dirfd, dir_name, S_IRWXU | S_IRWXG) != 0) {
+          log_error("mkdirat() failed: %s", strerror(errno));
+          close(dirfd);
+          free(disk_path);
+          free(path_copy);
+          free(storage_root);
+          return EVHTP_RES_SERVERR;
+        }
 
-  free(path_copy);
-  free(storage_root);
-  close(dirfd);
+#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);
+      close(prevfd);
+      if(dirfd == -1) {
+        log_error("failed to openat() next directory (\"%s\"): %s",
+                  dir_name, strerror(errno));
+        free(disk_path);
+        free(path_copy);
+        free(storage_root);
+        return EVHTP_RES_SERVERR;
+      }
+    }
+
+    free(path_copy);
+    free(storage_root);
+    close(dirfd);
+
+  } while(0);
 
   // open (and possibly create) file
   int fd = open(disk_path, O_NONBLOCK | O_CREAT | O_WRONLY | O_TRUNC,
@@ -123,9 +189,19 @@ evhtp_res storage_handle_put(evhtp_request_t *request) {
   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
+  // TODO: open (and write) file earlier in the request, so it doesn't have to be buffered completely.
   evbuffer_write(request->buffer_in, fd);
 
   char *content_type = "application/octet-stream; charset=binary";
@@ -135,21 +211,22 @@ evhtp_res storage_handle_put(evhtp_request_t *request) {
     content_type = content_type_header->val;
   }
   
-  if(content_type_to_xattr(fd, content_type) != 0) {
+  // remember content type in extended attributes
+  if(content_type_to_xattr(disk_path, content_type) != 0) {
     log_error("Setting xattr for content type failed. Ignoring.");
   }
 
   close(fd);
 
-  struct stat stat_buf;
+  // stat created file again to generate new etag
   memset(&stat_buf, 0, sizeof(struct stat));
   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);
@@ -157,10 +234,10 @@ evhtp_res storage_handle_put(evhtp_request_t *request) {
   free(etag_string);
   free(disk_path);
 
-  return EVHTP_RES_OK;
+  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
@@ -168,11 +245,12 @@ evhtp_res storage_handle_delete(evhtp_request_t *request) {
   }
 
   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;
@@ -182,20 +260,29 @@ evhtp_res storage_handle_delete(evhtp_request_t *request) {
       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);
@@ -203,7 +290,7 @@ evhtp_res storage_handle_delete(evhtp_request_t *request) {
       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
@@ -223,14 +310,15 @@ evhtp_res storage_handle_delete(evhtp_request_t *request) {
           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);
@@ -238,32 +326,18 @@ evhtp_res storage_handle_delete(evhtp_request_t *request) {
   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;
-}
-
-int json_buf_writer(char *buf, size_t count, void *arg) {
+size_t json_buf_writer(char *buf, size_t count, void *arg) {
   return evbuffer_add((struct evbuffer*)arg, buf, count);
 }
 
 // serve a directory response for the given request
 static evhtp_res serve_directory(evhtp_request_t *request, char *disk_path, struct stat *stat_buf) {
   size_t disk_path_len = strlen(disk_path);
-  struct evbuffer *buf = evbuffer_new();
-  if(buf == NULL) {
-    log_error("evbuffer_new() failed: %s", strerror(errno));
-    return 500;
-  }
+  struct evbuffer *buf = request->buffer_out;
   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);
@@ -271,7 +345,7 @@ static evhtp_res serve_directory(evhtp_request_t *request, char *disk_path, stru
   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);
@@ -299,9 +373,9 @@ static evhtp_res serve_directory(evhtp_request_t *request, char *disk_path, stru
     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_string(json, key_string, val_string);
+    json_write_key_val(json, key_string, val_string);
 
     free(val_string);
   }
@@ -310,122 +384,70 @@ static evhtp_res serve_directory(evhtp_request_t *request, char *disk_path, stru
 
   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);
 
-  // FIXME: why do I need to use *_chunk_* here???
-  evhtp_send_reply_chunk_start(request, EVHTP_RES_OK);
-  evhtp_send_reply_chunk(request, buf);
-  evhtp_send_reply_chunk_end(request);
   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 int 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) {
@@ -445,25 +467,12 @@ static int serve_file_head(evhtp_request_t *request, char *disk_path, struct sta
       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;
-  }
 
-  ADD_RESP_HEADER(request, "Content-Type", mime_type);
+  log_info("setting Content-Type of %s: %s", request->uri->path->full, mime_type);
+  ADD_RESP_HEADER_CP(request, "Content-Type", mime_type);
   ADD_RESP_HEADER_CP(request, "Content-Length", length_string);
   ADD_RESP_HEADER_CP(request, "ETag", etag_string);
 
-  evhtp_send_reply_chunk_start(request, 200);
-
   free(etag_string);
   free(length_string);
   if(free_mime_type) {
@@ -473,48 +482,82 @@ static int serve_file_head(evhtp_request_t *request, char *disk_path, struct sta
 }
 
 // serve a file body for the given request
-static int serve_file(evhtp_request_t *request, const char *disk_path, struct stat *stat_buf) {
+static evhtp_res serve_file(evhtp_request_t *request, const char *disk_path, struct stat *stat_buf) {
   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;
   }
-  struct evbuffer *buf = evbuffer_new();
-  while(evbuffer_read(buf, fd, 4096) != 0) {
-    evhtp_send_reply_chunk(request, buf);
-  }
-  evbuffer_free(buf);
-  evhtp_send_reply_chunk_end(request);
-  return 0;
+  while(evbuffer_read(request->buffer_out, fd, 4096) != 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;
@@ -526,60 +569,52 @@ static char *make_disk_path(char *user, char *path, char **storage_root) {
     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) {
-  char *storage_root = NULL;
-  char *disk_path = make_disk_path(request->uri->path->match_start,
-                                   request->uri->path->match_end,
-                                   &storage_root);
+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 *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 500;
+      return EVHTP_RES_SERVERR;
     } else {
-      return 404;
+      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 404;
-    }
-    // directory found
     if(include_body) {
       return serve_directory(request, disk_path, &stat_buf);
     } else {
-      return serve_file_head(request, disk_path, &stat_buf, "application/json");
+      evhtp_res head_status = serve_file_head(request, disk_path, &stat_buf, "application/json");
+      return head_status != 0 ? head_status : EVHTP_RES_OK;
     }
   } else {
     // file requested
-    if(S_ISDIR(stat_buf.st_mode)) {
-      // found, but is a directory
-      return 404;
-    }
-    // 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;
     }
     if(include_body) {
       return serve_file(request, disk_path, &stat_buf);
     } else {
-      return 0;
+      return EVHTP_RES_OK;
     }
   }
 }