0247a31998-03-11David Hedbor // This is a roxen module. Copyright © 1996 - 1998, Idonex AB.
57f45e1996-11-27Per Hedbor 
b1fca01996-11-12Per Hedbor // This is a virtual "file-system". // It will be located somewhere in the name-space of the server.
57f45e1996-11-27Per Hedbor // Also inherited by some of the other filesystems.
f6d62d1997-03-26Per Hedbor 
1909011998-02-24Henrik Grubbström (Grubba) inherit "module"; inherit "roxenlib"; inherit "socket";
f2e1da1998-04-24Per Hedbor constant cvs_version= "$Id: filesystem.pike,v 1.33 1998/04/24 09:30:00 per Exp $";
1909011998-02-24Henrik Grubbström (Grubba) constant thread_safe=1;
6682b91997-08-31Peter Bortas 
f6d62d1997-03-26Per Hedbor 
b1fca01996-11-12Per Hedbor #include <module.h>
48fa361997-04-05Per Hedbor #include <roxen.h>
14179b1997-01-29Per Hedbor #include <stat.h>
b1fca01996-11-12Per Hedbor  #if DEBUG_LEVEL > 20 # ifndef FILESYSTEM_DEBUG # define FILESYSTEM_DEBUG # endif #endif
e5bad21998-02-10Per Hedbor // import Array;
6396111997-02-14Per Hedbor 
8afb501998-04-15Henrik Grubbström (Grubba) #define TRACE_ENTER(A,B) do{if(id->misc->trace_enter)id->misc->trace_enter((A),(B));}while(0) #define TRACE_LEAVE(A) do{if(id->misc->trace_leave)id->misc->trace_leave((A));}while(0)
b1fca01996-11-12Per Hedbor int redirects, accesses, errors, dirlists; int puts, deletes; static int do_stat = 1; string status() { return ("<h2>Accesses to this filesystem</h2>"+ (redirects?"<b>Redirects</b>: "+redirects+"<br>":"")+ (accesses?"<b>Normal files</b>: "+accesses+"<br>" :"No file accesses<br>")+ (QUERY(put)&&puts?"<b>Puts</b>: "+puts+"<br>":"")+ (QUERY(delete)&&deletes?"<b>Deletes</b>: "+deletes+"<br>":"")+ (errors?"<b>Permission denied</b>: "+errors +" (not counting .htaccess)<br>":"")+ (dirlists?"<b>Directories</b>:"+dirlists+"<br>":"")); } void create() { defvar("mountpoint", "/", "Mount point", TYPE_LOCATION, "This is where the module will be inserted in the "+ "namespace of your server."); defvar("searchpath", "NONE", "Search path", TYPE_DIR, "This is where the module will find the files in the real "+ "file system"); #ifdef COMPAT defvar("html", 0, "All files are really HTML files", TYPE_FLAG|VAR_EXPERT, "If you set this variable, the filesystem will _know_ that all files " "are really HTML files. This might be useful now and then."); #endif
9b9f701997-08-12Per Hedbor  defvar(".files", 0, "Show hidden files", TYPE_FLAG|VAR_MORE,
b1fca01996-11-12Per Hedbor  "If set, hidden files will be shown in dirlistings and you " "will be able to retrieve them.");
9b9f701997-08-12Per Hedbor  defvar("dir", 1, "Enable directory listings per default", TYPE_FLAG|VAR_MORE,
b1fca01996-11-12Per Hedbor  "If set, you have to create a file named .www_not_browsable (" "or .nodiraccess) in a directory to disable directory listings." " If unset, a file named .www_browsable in a directory will " "_enable_ directory listings.\n");
9b9f701997-08-12Per Hedbor  defvar("tilde", 0, "Show backupfiles", TYPE_FLAG|VAR_MORE,
b1fca01996-11-12Per Hedbor  "If set, files ending with '~' or '#' or '.bak' will "+ "be shown in directory listings");
01a57e1998-01-04Peter Bortas  defvar("put", 0, "Handle the PUT method", TYPE_FLAG,
b1fca01996-11-12Per Hedbor  "If set, PUT can be used to upload files to the server.");
f6d62d1997-03-26Per Hedbor  defvar("delete", 0, "Handle the DELETE method", TYPE_FLAG,
b1fca01996-11-12Per Hedbor  "If set, DELETE can be used to delete files from the " "server.");
26d1321997-01-29David KÃ¥gedal  defvar("check_auth", 1, "Require authentication for modification",
b1fca01996-11-12Per Hedbor  TYPE_FLAG, "Only allow authenticated users to use methods other than " "GET and POST. If unset, this filesystem will be a _very_ " "public one (anyone can edit files located on it)");
14179b1997-01-29Per Hedbor  defvar("stat_cache", 1, "Cache the results of stat(2)",
9b9f701997-08-12Per Hedbor  TYPE_FLAG|VAR_MORE,
14179b1997-01-29Per Hedbor  "This can speed up the retrieval of files up to 60/70% if you" " use NFS, but it does use some memory.");
2e7c231997-06-10Henrik Grubbström (Grubba)  defvar("access_as_user", 0, "Access file as the logged in user",
9b9f701997-08-12Per Hedbor  TYPE_FLAG|VAR_MORE,
2e7c231997-06-10Henrik Grubbström (Grubba)  "EXPERIMENTAL. Access file as the logged in user.<br>\n" "This is useful for eg named-ftp.");
afb1581997-07-06Henrik Grubbström (Grubba) 
9b9f701997-08-12Per Hedbor  defvar("no_symlinks", 0, "Forbid access to symlinks", TYPE_FLAG|VAR_MORE,
afb1581997-07-06Henrik Grubbström (Grubba)  "EXPERIMENTAL.\n" "Forbid access to paths containing symbolic links.<br>\n" "NOTE: This can cause *alot* of lstat system-calls to be performed " "and can make the server much slower.");
b1fca01996-11-12Per Hedbor }
32ae661997-01-27Per Hedbor 
b1fca01996-11-12Per Hedbor mixed *register_module() { return ({ MODULE_LOCATION, "Filesystem", ("This is a virtual filesystem, use it to make files available to "+
fd0b6f1996-12-02Per Hedbor  "the users of your WWW-server. If you want to serve any 'normal' " "files from your server, you will have to have atleast one filesystem.")
b1fca01996-11-12Per Hedbor  }); } string path;
14179b1997-01-29Per Hedbor int stat_cache;
b1fca01996-11-12Per Hedbor  void start() {
8afc811998-02-04Per Hedbor #ifdef THREADS if(QUERY(access_as_user)) report_warning("It is not possible to use 'Access as user' when " "running with threads. Remove -DENABLE_THREADS from " "the start script if you really need this function\n"); #endif
b1fca01996-11-12Per Hedbor  path = QUERY(searchpath);
14179b1997-01-29Per Hedbor  stat_cache = QUERY(stat_cache);
b1fca01996-11-12Per Hedbor #ifdef FILESYSTEM_DEBUG perror("FILESYSTEM: Online at "+QUERY(mountpoint)+" (path="+path+")\n"); #endif } string query_location() { return QUERY(mountpoint); } mixed stat_file( mixed f, mixed id ) {
64c51c1997-10-11Henrik Grubbström (Grubba)  array fs; if(stat_cache && !id->pragma["no-cache"] && (fs=cache_lookup("stat_cache",path+f)))
1b8f6b1998-02-24Per Hedbor  return fs[0];
64c51c1997-10-11Henrik Grubbström (Grubba) 
8afc811998-02-04Per Hedbor #ifndef THREADS
1b8f6b1998-02-24Per Hedbor  object privs;
0216e21997-08-12Henrik Grubbström (Grubba)  if (((int)id->misc->uid) && ((int)id->misc->gid) && (QUERY(access_as_user))) { // NB: Root-access is prevented.
f7d9811997-09-12Per Hedbor  privs=Privs("Statting file", (int)id->misc->uid, (int)id->misc->gid );
0216e21997-08-12Henrik Grubbström (Grubba)  }
8afc811998-02-04Per Hedbor #endif
64c51c1997-10-11Henrik Grubbström (Grubba)  fs = file_stat(path + f); /* No security currently in this function */
1b8f6b1998-02-24Per Hedbor #ifndef THREADS
64c51c1997-10-11Henrik Grubbström (Grubba)  privs = 0;
1b8f6b1998-02-24Per Hedbor #endif
14179b1997-01-29Per Hedbor  if(!stat_cache) return fs;
1b8f6b1998-02-24Per Hedbor  cache_set("stat_cache", path+f, ({fs}));
14179b1997-01-29Per Hedbor  return fs;
b1fca01996-11-12Per Hedbor } string real_file( mixed f, mixed id ) { if(this->stat_file( f, id )) /* This filesystem might be inherited by other filesystem, therefore
27b0e11996-11-26Per Hedbor  'this' */
b1fca01996-11-12Per Hedbor  return path + f; } int dir_filter_function(string f) { if(f[0]=='.' && !QUERY(.files)) return 0; if(!QUERY(tilde) && backup_extension(f)) return 0; return 1; } array find_dir( string f, object id ) { mixed ret; array dir;
0216e21997-08-12Henrik Grubbström (Grubba)  object privs;
8afc811998-02-04Per Hedbor #ifndef THREADS
0216e21997-08-12Henrik Grubbström (Grubba)  if (((int)id->misc->uid) && ((int)id->misc->gid) && (QUERY(access_as_user))) { // NB: Root-access is prevented.
f7d9811997-09-12Per Hedbor  privs=Privs("Getting dir", (int)id->misc->uid, (int)id->misc->gid );
0216e21997-08-12Henrik Grubbström (Grubba)  }
8afc811998-02-04Per Hedbor #endif
0216e21997-08-12Henrik Grubbström (Grubba) 
64c51c1997-10-11Henrik Grubbström (Grubba)  if(!(dir = get_dir( path + f ))) { privs = 0;
b1fca01996-11-12Per Hedbor  return 0;
64c51c1997-10-11Henrik Grubbström (Grubba)  }
b1fca01996-11-12Per Hedbor 
0216e21997-08-12Henrik Grubbström (Grubba)  privs = 0;
b1fca01996-11-12Per Hedbor  if(!QUERY(dir)) // Access to this dir is allowed. if(search(dir, ".www_browsable") == -1) { errors++; return 0; } // Access to this dir is not allowed. if(sizeof(dir & ({".nodiraccess",".www_not_browsable",".nodir_access"}))) { errors++; return 0; } dirlists++; // Pass _all_ files, hide none. if(QUERY(tilde) && QUERY(.files)) /* This is quite a lot faster */ return dir;
e5bad21998-02-10Per Hedbor  return Array.filter(dir, dir_filter_function);
b1fca01996-11-12Per Hedbor } mapping putting = ([]); void done_with_put( array(object) id ) { // perror("Done with put.\n"); id[0]->close(); id[1]->write("HTTP/1.0 200 Created\r\nContent-Length: 0\r\n\r\n"); id[1]->close(); m_delete(putting, id[1]); destruct(id[0]); destruct(id[1]); } void got_put_data( array (object) id, string data ) { // perror(strlen(data)+" .. "); id[0]->write( data ); putting[id[1]] -= strlen(data); if(putting[id[1]] <= 0) done_with_put( id ); }
14179b1997-01-29Per Hedbor int _file_size(string X,object id) { array fs; if(!id->pragma["no-cache"]&&(fs=cache_lookup("stat_cache",(X)))) {
1b8f6b1998-02-24Per Hedbor  id->misc->stat = fs[0]; return fs[0]?fs[0][ST_SIZE]:-1;
14179b1997-01-29Per Hedbor  } if(fs = file_stat(X)) { id->misc->stat = fs;
1b8f6b1998-02-24Per Hedbor  cache_set("stat_cache",(X),({fs}));
14179b1997-01-29Per Hedbor  return fs[ST_SIZE];
1b8f6b1998-02-24Per Hedbor  } else cache_set("stat_cache",(X),({0}));
14179b1997-01-29Per Hedbor  return -1; }
6396111997-02-14Per Hedbor #define FILE_SIZE(X) (stat_cache?_file_size((X),id):Stdio.file_size(X))
14179b1997-01-29Per Hedbor 
afb1581997-07-06Henrik Grubbström (Grubba) int contains_symlinks(string root, string path) { array arr = path/"/"; foreach(arr - ({ "" }), path) { root += "/" + path; if (arr = file_stat(root, 1)) { if (arr[1] == -3) { return(1); } } else { return(0); } } return(0); }
b1fca01996-11-12Per Hedbor mixed find_file( string f, object id ) {
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_ENTER("find_file(\""+f+"\")", 0);
b1fca01996-11-12Per Hedbor  object o; int size; string tmp;
afb1581997-07-06Henrik Grubbström (Grubba)  string oldf = f;
f2e1da1998-04-24Per Hedbor  f = path + f;
da9e9f1998-04-24Per Hedbor #ifdef __NT__ if(f[-1]=='/') f = f[..strlen(f)-2];
b1fca01996-11-12Per Hedbor #endif
f2e1da1998-04-24Per Hedbor  size = FILE_SIZE( f );
b1fca01996-11-12Per Hedbor  switch(id->method) { case "GET": case "HEAD": case "POST": switch(-size) { case 1:
8afb501998-04-15Henrik Grubbström (Grubba)  case 3: case 4: TRACE_LEAVE("No file");
b1fca01996-11-12Per Hedbor  return 0; /* Is no-file */ case 2:
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("Is directory");
b1fca01996-11-12Per Hedbor  return -1; /* Is dir */ default: if(f[ -1 ] == '/') /* Trying to access file with '/' appended */ {
5e429c1997-05-25Wilhelm Köhler  /* Do not try redirect on top level directory */ if(sizeof(id->not_query) < 2) return 0;
b1fca01996-11-12Per Hedbor  redirects++;
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("Redirecting to \"" + id->not_query[..sizeof(id->not_query)-2] + "\"");
b1fca01996-11-12Per Hedbor  return http_redirect(id->not_query[..sizeof(id->not_query)-2], id); } if(!id->misc->internal_get && QUERY(.files) && (tmp = (id->not_query/"/")[-1])
8afb501998-04-15Henrik Grubbström (Grubba)  && tmp[0] == '.') { TRACE_LEAVE("Is .-file");
b1fca01996-11-12Per Hedbor  return 0;
8afb501998-04-15Henrik Grubbström (Grubba)  }
8afc811998-02-04Per Hedbor #ifndef THREADS
1b8f6b1998-02-24Per Hedbor  object privs;
2e7c231997-06-10Henrik Grubbström (Grubba)  if (((int)id->misc->uid) && ((int)id->misc->gid) && (QUERY(access_as_user))) {
0216e21997-08-12Henrik Grubbström (Grubba)  // NB: Root-access is prevented.
f7d9811997-09-12Per Hedbor  privs=Privs("Getting file", (int)id->misc->uid, (int)id->misc->gid );
2e7c231997-06-10Henrik Grubbström (Grubba)  }
8afc811998-02-04Per Hedbor #endif
2e7c231997-06-10Henrik Grubbström (Grubba) 
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_ENTER("Opening file \"" + f + "\"", 0);
b1fca01996-11-12Per Hedbor  o = open( f, "r" );
1b8f6b1998-02-24Per Hedbor #ifndef THREADS
2e7c231997-06-10Henrik Grubbström (Grubba)  privs = 0;
1b8f6b1998-02-24Per Hedbor #endif
2e7c231997-06-10Henrik Grubbström (Grubba) 
afb1581997-07-06Henrik Grubbström (Grubba)  if(!o || (QUERY(no_symlinks) && (contains_symlinks(path, oldf))))
b1fca01996-11-12Per Hedbor  { errors++; report_error("Open of " + f + " failed. Permission denied.\n");
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE(""); TRACE_LEAVE("Permission denied.");
b1fca01996-11-12Per Hedbor  return http_low_answer(403, "<h2>File exists, but access forbidden " "by user</h2>"); } id->realfile = f;
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("");
b1fca01996-11-12Per Hedbor  accesses++; #ifdef COMPAT
8afb501998-04-15Henrik Grubbström (Grubba)  if(QUERY(html)) {/* Not very likely, really.. */ TRACE_LEAVE("Compat return");
b1fca01996-11-12Per Hedbor  return ([ "type":"text/html", "file":o, ]);
8afb501998-04-15Henrik Grubbström (Grubba)  }
b1fca01996-11-12Per Hedbor #endif
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("Normal return");
b1fca01996-11-12Per Hedbor  return o; } break; case "PUT": if(!QUERY(put))
f6d62d1997-03-26Per Hedbor  { id->misc->error_code = 405;
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("PUT disallowed");
b1fca01996-11-12Per Hedbor  return 0;
f6d62d1997-03-26Per Hedbor  }
8afb501998-04-15Henrik Grubbström (Grubba)  if(QUERY(check_auth) && (!id->auth || !id->auth[0])) { TRACE_LEAVE("PUT: Permission denied");
f6d62d1997-03-26Per Hedbor  return http_auth_required("foo", "<h1>Permission to 'PUT' files denied</h1>");
8afb501998-04-15Henrik Grubbström (Grubba)  }
b1fca01996-11-12Per Hedbor  puts++;
f6d62d1997-03-26Per Hedbor  object privs;
8afc811998-02-04Per Hedbor // #ifndef THREADS // Ouch. This is is _needed_. Well well...
2e7c231997-06-10Henrik Grubbström (Grubba)  if (((int)id->misc->uid) && ((int)id->misc->gid)) {
0216e21997-08-12Henrik Grubbström (Grubba)  // NB: Root-access is prevented.
f7d9811997-09-12Per Hedbor  privs=Privs("Saving file", (int)id->misc->uid, (int)id->misc->gid );
2e7c231997-06-10Henrik Grubbström (Grubba)  }
8afc811998-02-04Per Hedbor // #endif
f6d62d1997-03-26Per Hedbor 
afb1581997-07-06Henrik Grubbström (Grubba)  if (QUERY(no_symlinks) && (contains_symlinks(path, oldf))) {
64c51c1997-10-11Henrik Grubbström (Grubba)  privs = 0;
afb1581997-07-06Henrik Grubbström (Grubba)  errors++; report_error("Creation of " + f + " failed. Permission denied.\n");
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("PUT: Contains symlinks. Permission denied");
afb1581997-07-06Henrik Grubbström (Grubba)  return http_low_answer(403, "<h2>Permission denied.</h2>"); }
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_ENTER("PUT: Accepted", 0);
b1fca01996-11-12Per Hedbor  rm( f ); mkdirhier( f );
8afb501998-04-15Henrik Grubbström (Grubba)  /* Clear the stat-cache for this file */ if (stat_cache) { cache_set("stat_cache", f, 0); }
b1fca01996-11-12Per Hedbor  object to = open(f, "wc");
2e7c231997-06-10Henrik Grubbström (Grubba)  privs = 0;
b1fca01996-11-12Per Hedbor  if(!to)
f6d62d1997-03-26Per Hedbor  { id->misc->error_code = 403;
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("PUT: Open failed"); TRACE_LEAVE("Failure");
f6d62d1997-03-26Per Hedbor  return 0; }
b1fca01996-11-12Per Hedbor  putting[id->my_fd]=id->misc->len; if(id->data && strlen(id->data)) { putting[id->my_fd] -= strlen(id->data); to->write( id->data ); }
8afb501998-04-15Henrik Grubbström (Grubba)  if(!putting[id->my_fd]) { TRACE_LEAVE("PUT: Just a string"); TRACE_LEAVE("Put: Success");
b1fca01996-11-12Per Hedbor  return http_string_answer("Ok");
8afb501998-04-15Henrik Grubbström (Grubba)  }
b1fca01996-11-12Per Hedbor 
a2f3461997-06-12Henrik Grubbström (Grubba)  if(id->clientprot == "HTTP/1.1") {
b1fca01996-11-12Per Hedbor  id->my_fd->write("HTTP/1.1 100 Continue\r\n");
a2f3461997-06-12Henrik Grubbström (Grubba)  }
b1fca01996-11-12Per Hedbor  id->my_fd->set_id( ({ to, id->my_fd }) ); id->my_fd->set_nonblocking(got_put_data, 0, done_with_put);
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("PUT: Pipe in progress"); TRACE_LEAVE("PUT: Success so far");
b1fca01996-11-12Per Hedbor  return http_pipe_in_progress(); break; case "DELETE": if(!QUERY(delete) || size==-1)
f6d62d1997-03-26Per Hedbor  { id->misc->error_code = 405;
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("DELETE: Disabled");
b1fca01996-11-12Per Hedbor  return 0;
f6d62d1997-03-26Per Hedbor  }
8afb501998-04-15Henrik Grubbström (Grubba)  if(QUERY(check_auth) && (!id->auth || !id->auth[0])) { TRACE_LEAVE("DELETE: Permission denied");
b1fca01996-11-12Per Hedbor  return http_low_answer(403, "<h1>Permission to DELETE file denied</h1>");
8afb501998-04-15Henrik Grubbström (Grubba)  }
b1fca01996-11-12Per Hedbor 
afb1581997-07-06Henrik Grubbström (Grubba)  if (QUERY(no_symlinks) && (contains_symlinks(path, oldf))) { errors++; report_error("Deletion of " + f + " failed. Permission denied.\n");
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("DELETE: Contains symlinks");
afb1581997-07-06Henrik Grubbström (Grubba)  return http_low_answer(403, "<h2>Permission denied.</h2>"); }
dbf74f1997-06-12Marcus Comstedt  report_notice("DELETING the file "+f+"\n");
b1fca01996-11-12Per Hedbor  accesses++;
f6d62d1997-03-26Per Hedbor 
2e7c231997-06-10Henrik Grubbström (Grubba)  if (((int)id->misc->uid) && ((int)id->misc->gid)) {
0216e21997-08-12Henrik Grubbström (Grubba)  // NB: Root-access is prevented.
f7d9811997-09-12Per Hedbor  privs=Privs("Deleting file", id->misc->uid, id->misc->gid );
2e7c231997-06-10Henrik Grubbström (Grubba)  }
f6d62d1997-03-26Per Hedbor 
8afb501998-04-15Henrik Grubbström (Grubba)  /* Clear the stat-cache for this file */ if (stat_cache) { cache_set("stat_cache", f, 0); }
f6d62d1997-03-26Per Hedbor  if(!rm(f)) {
64c51c1997-10-11Henrik Grubbström (Grubba)  privs = 0;
f6d62d1997-03-26Per Hedbor  id->misc->error_code = 405;
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("DELETE: Failed");
f6d62d1997-03-26Per Hedbor  return 0; }
64c51c1997-10-11Henrik Grubbström (Grubba)  privs = 0;
f6d62d1997-03-26Per Hedbor  deletes++;
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("DELETE: Success");
b1fca01996-11-12Per Hedbor  return http_low_answer(200,(f+" DELETED from the server")); default:
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("Not supported");
b1fca01996-11-12Per Hedbor  return 0; } report_error("Not reached..\n");
8afb501998-04-15Henrik Grubbström (Grubba)  TRACE_LEAVE("Not reached");
b1fca01996-11-12Per Hedbor  return 0; } string query_name() { return sprintf("<i>%s</i> mounted on <i>%s</i>", query("searchpath"), query("mountpoint")); }