Back to index

avfs  1.0.1
dav.c
Go to the documentation of this file.
00001 /*  
00002     AVFS: A Virtual File System Library
00003     Copyright (C) 1998  Miklos Szeredi <miklos@szeredi.hu>
00004     
00005     This file can be distributed either under the GNU LGPL, or under
00006     the GNU GPL. See the file COPYING.LIB and COPYING. 
00007 
00008     DAV module (see http://www.webdav.org/) using Neon
00009     DAV client library, by Justin Mason <jm-avfs@jmason.org>.
00010 */
00011 
00012   /* TODO -- http_request_auth implementation */
00013   /* TODO -- PUT */
00014   /* TODO -- make GET more efficient by not getting entire file
00015    * before returning from dav_get() */
00016 
00017 #include "dav.h"
00018 #include "avfs.h"
00019 #include "version.h"
00020 #include "remote.h"
00021 #include "prog.h"
00022 #include "filebuf.h"
00023 #include "passwords.h"
00024 
00025 
00026 #include <unistd.h>
00027 #include <fcntl.h>
00028 #include <sys/stat.h>
00029 #include <stdlib.h>
00030 #include <string.h>
00031 #include <assert.h>
00032 
00033 
00034 /* --------------------------------------------------------------------- */
00035 
00036 static char AV_UserAgent[128];
00037 
00038 static struct uri av_dav_uri_defaults = {
00039     "http", NULL, 80, NULL
00040 };
00041 
00042 struct davlocalfile {
00043     int running;
00044     char *tmpfile;
00045     char *url;
00046     avoff_t currsize;
00047     int fd;
00048 };
00049 
00050 /* --------------------------------------------------------------------- */
00051 
00052 /*
00053 char *
00054 davlocalfile_to_string (struct davlocalfile *lf)
00055 {
00056     static char buf[1024];
00057     snprintf (buf, 1023,
00058         "[davlocalfile: tmpf=%s url=%s running=%d currsz=%d]",
00059         lf->tmpfile, lf->url, lf->running, (int) lf->currsize);
00060     return buf;
00061 }
00062 */
00063 
00064 /* ---------------------------------------------------------------------- */
00065 
00066 static char *dav_hostpath_to_url (char *urlbuf, int buflen,
00067                                         const struct remhostpath *hp)
00068 {
00069     const char *rawp, *pathp;
00070     int len;
00071 
00072     *urlbuf = '\0';
00073     av_log(AVLOG_DEBUG, "DAV: hostpath-to-URL: host=%s path='%s'",
00074                                 hp->host, hp->path);
00075 
00076     /* now rewrite the host bit into the urlbuf, adding:
00077      * - the protocol bit if http: is specified, as http://
00078      * - /s instead of :s
00079      * - a trailing slash
00080      */
00081     len = 0; rawp = hp->host;
00082     if (!strncmp (rawp, "http:", 5)) {
00083         len += snprintf (urlbuf+len, buflen-len, "http://");
00084         rawp += 5;
00085         while (*rawp == '/') { rawp++; }
00086     }
00087 
00088     for ( ; *rawp != '\0'; rawp++) {
00089         /* TODO: allow colons, or just pipes? */
00090         if (/* *rawp == ':' || */ *rawp == '|') {
00091             urlbuf[len] = '/';
00092         } else {
00093             urlbuf[len] = *rawp;
00094         }
00095         len++;
00096     }
00097 
00098     if (len > 0 && urlbuf[len-1] == '/') {
00099         len--; urlbuf[len] = '\0';
00100     }
00101 
00102     /* add the path, if it's non-empty */
00103     for (pathp = hp->path; *pathp == '/'; pathp++);
00104     if (pathp != '\0') {
00105         len += snprintf (urlbuf+len, buflen-len, "/%s", pathp);
00106     }
00107 
00111     av_log(AVLOG_DEBUG, "DAV: rewritten URL = '%s'", urlbuf);
00112 
00113     return urlbuf;
00114 }
00115 
00116 /* ---------------------------------------------------------------------- */
00117 
00118 static void dav_free_localfile(struct davlocalfile *lf)
00119 {
00120   av_free(lf->url);
00121 }
00122 
00123 /* ---------------------------------------------------------------------- */
00124 
00125 static int
00126 dav_supply_creds(int is_for_proxy, void *userdata, const char *realm,
00127               const char *hostname, char **username, char **password)
00128 {
00129     struct pass_session *pass;
00130     struct davdata *davdat = (struct davdata *) userdata;
00131 
00132     pass = pass_get_password (&(davdat->sessions), realm, hostname);
00133     if (pass == NULL) {
00134        return -1;
00135     }
00136 
00137     /* TODO: really need to dup this? will neon free it? */
00138     *username = ne_strdup (pass->username);
00139     *password = ne_strdup (pass->password);
00140     return 0;
00141 }
00142 
00143 static int
00144 dav_supply_creds_server(void *userdata, const char *realm,
00145               const char *hostname, char **username, char **password)
00146 {
00147     return dav_supply_creds(0, userdata, realm, hostname, username, password);
00148 }
00149 
00150 static int
00151 dav_supply_creds_proxy(void *userdata, const char *realm, 
00152               const char *hostname, char **username, char **password) 
00153 {
00154     return dav_supply_creds(1, userdata, realm, hostname, username, password);
00155 }
00156 
00157 /* ---------------------------------------------------------------------- */
00158 
00159 static int
00160 av_dav_conn_init (struct av_dav_conn *conn, struct davdata *davdat)
00161 {
00162     conn->sesh = http_session_create();
00163 
00164     /* TODO: provide proxy support from http_proxy env var */
00165     /* TODO: first make sure neon doesn't automatically do this ;) */
00166     /* http_session_proxy(sess, "proxy.myisp.com", 8080); */
00167     http_set_expect100 (conn->sesh, 1);
00168     http_set_useragent (conn->sesh, AV_UserAgent);
00169     http_set_server_auth (conn->sesh, dav_supply_creds_server, davdat);
00170     http_set_proxy_auth (conn->sesh, dav_supply_creds_proxy, davdat);
00171 
00172     return 0;
00173 }
00174 
00175 static struct av_dav_conn *
00176 new_dav_conn (struct davdata *davdat)
00177 {
00178     int i;
00179     struct av_dav_conn *conn;
00180 
00181     for (i = 0; i < AV_MAX_DAV_CONNS; i++) {
00182         conn = &(davdat->allconns[i]);
00183         /* skip the busy ones */
00184         if (conn->isbusy) { continue; }
00185 
00186         if (conn->sesh == NULL) {
00187             /* NULL session? This one hasn't been initted yet. */
00188            av_dav_conn_init (conn, davdat);
00189             av_log(AVLOG_DEBUG, "DAV: created new HTTP session");
00190         }
00191         conn->isbusy = 1;
00192         return conn;
00193     }
00194 
00195     av_log(AVLOG_ERROR, "DAV: out of connections");
00196     return NULL;
00197 }
00198 
00199 /* ---------------------------------------------------------------------- */
00200 
00201 static int
00202 http_error_to_errno (const char *method, int httpret, const char *errstr)
00203 {
00204     int errval = -EIO;
00205 
00206     av_log(AVLOG_ERROR, "DAV: %s failed: (neon err=%d) \"%s\"",
00207                                     method, httpret, errstr);
00208 
00209     switch (httpret) {
00210         case HTTP_ERROR:
00211         /* HTTP_ERROR (1) Generic error; use http_get_error(session) */
00212         /* TODO -- fill out more HTTP errors here */
00213 
00214         if (!strncmp (errstr, "404", 3)) {
00215             errval = -ENOENT;
00216         } else if (!strncmp (errstr, "403", 3)) {
00217             errval = -EACCES;
00218         } else if (!strncmp (errstr, "405", 3)) {
00219             errval = -EACCES;
00220         }
00221         break;
00222 
00223         case HTTP_LOOKUP:
00224         /* HTTP_LOOKUP (3) Name lookup failed */
00225         errval = -ECONNREFUSED;
00226         break;
00227 
00228         case HTTP_AUTH:
00229         /* HTTP_AUTH (4) User authentication failed on server */
00230        errval = -EACCES;
00231         break;
00232 
00233         case HTTP_AUTHPROXY:
00234         /* HTTP_AUTHPROXY (5) User authentication failed on proxy */
00235        errval = -EACCES;
00236         break;
00237 
00238         case HTTP_SERVERAUTH:
00239         /* HTTP_SERVERAUTH (6) Server authentication failed */
00240        errval = -EACCES;
00241         break;
00242 
00243         case HTTP_PROXYAUTH:
00244         /* HTTP_PROXYAUTH (7) Proxy authentication failed */
00245        errval = -EACCES;
00246         break;
00247 
00248         case HTTP_CONNECT:
00249         /* HTTP_CONNECT (8) Could not connect to server */
00250         errval = -ECONNREFUSED;
00251         break;
00252 
00253         case HTTP_TIMEOUT:
00254         /* HTTP_TIMEOUT (9) Connection timed out */
00255         errval = -ETIMEDOUT;
00256         break;
00257 
00258         case HTTP_FAILED:
00259         /* HTTP_FAILED (10) The precondition failed */
00260         errval = -ENXIO;
00261         break;
00262 
00263         default:
00264         av_log (AVLOG_ERROR, "Unknown HTTP error code for %s: %d %s",
00265                 method, httpret, errstr);
00266        errval = -ENXIO;
00267         break;
00268     }
00269     av_log (AVLOG_DEBUG, "returning errno %d", errval);
00270     return errval;
00271 }
00272 
00273 /* ---------------------------------------------------------------------- */
00274 
00275 static void av_get_cb (void *userdata, const char *buf, size_t len)
00276 {
00277     struct davlocalfile *lf = (struct davlocalfile *) userdata;
00278     int res;
00279 
00280     av_log(AVLOG_DEBUG, "DAV: GET cb: writing %d", len);
00281     res = write (lf->fd, buf, len);
00282     if (res < 0) {
00283         av_log (AVLOG_ERROR, "DAV: write failed: %s", strerror(errno));
00284     }
00285     if (res != len) {
00286         av_log (AVLOG_ERROR, "DAV: short write to tmpfile (%i/%i)",
00287                 res, len);
00288     }
00289     lf->currsize += len;
00290 }
00291 
00292 static int dav_http_get (struct davdata *davdat, struct davlocalfile *lf)
00293 {
00294     const char *err;
00295     int res;
00296     struct av_dav_conn *conn = NULL;
00297 
00298     conn = new_dav_conn (davdat);
00299     if (conn == NULL) { return -1; }
00300 
00301     lf->fd = -1;
00302 
00303     if (uri_parse (lf->url, &(conn->uri), &av_dav_uri_defaults) != 0
00304         || conn->uri.path == NULL
00305         || conn->uri.host == NULL)
00306     {
00307         av_log(AVLOG_ERROR, "DAV: Invalid URI '%s'", lf->url);
00308         res = -1; goto error;
00309     }
00310 
00311     lf->fd = open (lf->tmpfile, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0700);
00312     if (lf->fd < 0) {
00313         av_log(AVLOG_ERROR, "DAV: failed to write to '%s': %s", lf->tmpfile,
00314                 strerror (errno));
00315         res = -1; goto error;
00316     }
00317 
00318     http_session_server (conn->sesh, conn->uri.host, conn->uri.port);
00319 
00320     /* unfortunately the Neon API doesn't allow partial reads.
00321      * Perhaps redo this using the http.c code to avoid having
00322      * to download the entire file first */
00323     av_log(AVLOG_DEBUG, "DAV: GETting '%s'", lf->url);
00324     res = http_read_file (conn->sesh, lf->url, av_get_cb, lf);
00325     close (lf->fd); lf->fd = -1;
00326 
00327     if (res != HTTP_OK) {
00328         err = http_get_error(conn->sesh);
00329         res = http_error_to_errno ("GET", res, err);
00330         goto error;
00331     }
00332 
00333     conn->isbusy = 0;
00334     return 0;
00335 
00336 error:
00337     conn->isbusy = 0;
00338     if (lf->fd > 0) { close (lf->fd); lf->fd = -1; }
00339     return res;
00340 }
00341 
00342 /* ---------------------------------------------------------------------- */
00343 
00344 /*
00345  * TODO: rewrite using new "remote" API once it stabilises for writes
00346  */
00347 #if 0
00348 static int dav_http_put (struct file *fil, struct davlocalfile *lf)
00349 {
00350   const char *err;
00351   struct av_dav_conn *conn;
00352   FILE *fin = NULL;
00353   int res;
00354 
00355   conn = new_dav_conn (davdat);
00356   if (conn == NULL) { return -1; }
00357 
00358   if (uri_parse (lf->url, &(conn->uri), &av_dav_uri_defaults) != 0
00359     || conn->uri.path == NULL
00360     || conn->uri.host == NULL)
00361   {
00362     av_log(AVLOG_ERROR, "DAV: Invalid URI '%s'", lf->url);
00363     res = -1; goto error;
00364   }
00365 
00366   http_session_server (conn->sesh, conn->uri.host, conn->uri.port);
00367   res = lseek (lf->fd, 0, SEEK_SET);
00368   if (res < 0) {
00369     res = -errno; goto error;
00370   }
00371 
00372   fin = fdopen (lf->fd, "r");
00373 
00374   /* unfortunately the Neon API doesn't allow partial writes.
00375    * Perhaps redo this using the http.c code
00376    */
00377   res = http_put (conn->sesh, lf->url, fin);
00378   if (res != HTTP_OK) {
00379     err = http_get_error(conn->sesh);
00380     res = http_error_to_errno ("PUT", res, err);
00381     goto error;
00382   }
00383 
00384   conn->isbusy = 0;
00385   if (fin != NULL) { fclose (fin); }
00386   close (lf->fd);
00387   return 0;
00388 
00389 error:
00390   conn->isbusy = 0;
00391   if (fin != NULL) { fclose (fin); }
00392   close (lf->fd);
00393   return res;
00394 }
00395 #endif
00396 
00397 /* ---------------------------------------------------------------------- */
00398 
00399 static int dav_res_stat_to_avstat (struct av_dav_resource *res,
00400                 struct avstat *stbuf)
00401 {
00402     stbuf->mtime.sec = res->modtime;
00403     stbuf->mtime.nsec = 0;
00404     stbuf->atime = stbuf->mtime;
00405     stbuf->ctime = stbuf->mtime;
00406 
00407     stbuf->dev = 1;
00408     stbuf->ino = 1;
00409     stbuf->nlink = 1;
00410     stbuf->uid = 0;
00411     stbuf->gid = 0;
00412     stbuf->blksize = 1024;
00413 
00414     switch (res->type) {
00415     case resr_normal:
00416         stbuf->size = res->size;
00417         stbuf->blocks = AV_DIV(stbuf->size, 1024);
00418         if (res->is_executable) {
00419             stbuf->mode = AV_IFREG | 0777;
00420         } else {
00421             stbuf->mode = AV_IFREG | 0666;
00422         }
00423         break;
00424 
00425     case resr_reference:
00426         av_log(AVLOG_WARNING, "DAV: reference: TODO %d", res->type);
00427         /* symbolic link, doesn't seem to be supported by mod_dav
00428          * anyway
00429          */
00430         return -1;
00431 
00432     case resr_collection:
00433         stbuf->size = res->size;
00434         stbuf->blocks = AV_DIV(stbuf->size, 1024);
00435         stbuf->mode = AV_IFDIR | 0777;
00436         break;
00437 
00438     default:
00439         av_log(AVLOG_WARNING, "DAV: unknown resource type %d", res->type);
00440         return -1;
00441     }
00442 
00443     return 0;
00444 }
00445 
00446 /* ---------------------------------------------------------------------- */
00447 
00448 static int
00449 populate_av_tree_from_reslist (struct remdirlist *dl, struct av_dav_conn *conn,
00450                                struct av_dav_resource *reslist)
00451 {
00452   struct av_dav_resource *current, *next;
00453   char *shortname, *endchar;
00454   int pathlen;
00455 
00456   pathlen = strlen (conn->uri.path);
00457 
00458   for (current = reslist; current!=NULL; current = next) {
00459       next = current->next;
00460 
00461       /* skip path at start of name, if possible. Also trim
00462        * out any slashes we won't need. */
00463       if (!strncmp (current->uri, conn->uri.path, pathlen)) {
00464           shortname = current->uri + pathlen;
00465           while (*shortname == '/') {
00466               shortname++;
00467           }
00468           if (shortname[0] == '\0' || !strcmp (shortname, "/")) {
00469             shortname = ".";
00470           }
00471       } else {
00472           shortname = current->uri;
00473       }
00474 
00475       endchar = shortname + (strlen(shortname)-1);
00476       if (*endchar == '/') { *endchar = '\0'; }
00477 
00478       {
00479           struct avstat stbuf;
00480           char *linkname = NULL;
00481           char *remname;
00482 
00483           remname = dl->hostpath.path;
00484 
00485           if (dav_res_stat_to_avstat (current, &stbuf) < 0) {
00486               av_log (AVLOG_WARNING,
00487                                 "DAV: parsing direntry: to_avstat failed");
00488               goto skip;
00489           }
00490 
00491           av_log (AVLOG_DEBUG, "DAV: adding direntry \"%s\" mode=0%o",
00492                                         remname, stbuf.mode);
00493           av_remote_add (dl, remname, linkname, &stbuf);
00494       }
00495 
00496   skip:
00497       free_resource (current);
00498   }
00499 
00500   return 0;
00501 }
00502 
00503 /* ---------------------------------------------------------------------- */
00504 
00505 static int dav_list(struct remote *rem, struct remdirlist *dl)
00506 {
00507     char urlbuf[512];
00508     int res;
00509     struct davdata *davdat = (struct davdata *) rem->data;
00510     char *url;
00511     struct av_dav_conn *conn;
00512     struct av_dav_resource *reslist = NULL;
00513     const char *err;
00514 
00515     url = dav_hostpath_to_url (urlbuf, 511, &(dl->hostpath));
00516     av_log (AVLOG_DEBUG, "DAV: dav_list called on '%s' flags=%x",
00517                                         url, dl->flags);
00518 
00519     conn = new_dav_conn (davdat);
00520     if (conn == NULL) { return -1; }
00521 
00522     if (uri_parse (url, &(conn->uri), &av_dav_uri_defaults) != 0
00523       || conn->uri.path == NULL
00524       || conn->uri.host == NULL)
00525     {
00526         av_log(AVLOG_ERROR, "DAV: Invalid URI '%s'", url);
00527         res = -1; goto error;
00528     }
00529 
00530     http_session_server (conn->sesh, conn->uri.host, conn->uri.port);
00531     res = fetch_resource_list (conn, conn->uri.path, 1, 1, &reslist);
00532     if (res != HTTP_OK) {
00533         err = http_get_error(conn->sesh);
00534         res = http_error_to_errno ("PROPFIND", res, err);
00535         goto error;
00536     }
00537 
00538     if (reslist == NULL) {
00539         av_log (AVLOG_WARNING, "DAV: no reslist");
00540         res = -1; goto error;
00541     }
00542 
00543     if (populate_av_tree_from_reslist (dl, conn, reslist) < 0) {
00544         res = -1; goto error;
00545     }
00546 
00547     conn->isbusy = 0;
00548     return 0;
00549 
00550 error:
00551     conn->isbusy = 0;
00552     return res;
00553 }
00554 
00555 /* ---------------------------------------------------------------------- */
00556 
00557 static int dav_get(struct remote *rem, struct remgetparam *gp)
00558 {
00559     int res;
00560     char urlbuf[512];
00561     struct davdata *davdat = (struct davdata *) rem->data;
00562     struct davlocalfile *lf;
00563     char *tmpfile;
00564 
00565     res = av_get_tmpfile(&tmpfile);
00566     if(res < 0) {
00567         return res;
00568     }
00569 
00570     AV_NEW_OBJ(lf, dav_free_localfile);
00571 
00572     lf->url = av_strdup (dav_hostpath_to_url
00573                                     (urlbuf, 511, &(gp->hostpath)));
00574     lf->tmpfile = tmpfile;
00575     lf->currsize = 0;
00576     lf->fd = -1;
00577 
00578     res = dav_http_get (davdat, lf);
00579 
00580     if (res < 0) {
00581         av_unref_obj(lf);
00582         av_free(lf->url);
00583         av_del_tmpfile(lf->tmpfile);
00584         return res;
00585     }
00586 
00587     gp->data = lf;
00588     gp->localname = lf->tmpfile;
00589 
00590     return 0;
00591 }
00592 
00593 /* ---------------------------------------------------------------------- */
00594 
00595 static int dav_wait(struct remote *rem, void *data, avoff_t end)
00596 {
00597     return 1;
00598 }
00599 
00600 /* ---------------------------------------------------------------------- */
00601 
00602 static int dav_init_ctl(struct vmodule *module, struct davdata *davdat)
00603 {
00604     int res;
00605     struct namespace *ns;
00606     struct statefile *stf;
00607     struct entry *ent;
00608     struct avfs *avfs;
00609 
00610     res = av_state_new(module, "dav_ctl", &ns, &avfs);
00611     if(res < 0)
00612         return res;
00613 
00614     ent = av_namespace_lookup(ns, NULL, "username");
00615     AV_NEW(stf);
00616     stf->data = &(davdat->sessions);
00617     stf->get = pass_username_get;
00618     stf->set = pass_username_set;
00619     av_namespace_set(ent, stf);
00620 
00621     ent = av_namespace_lookup(ns, NULL, "password");
00622     AV_NEW(stf);
00623     stf->data = &(davdat->sessions);
00624     stf->get = pass_password_get;
00625     stf->set = pass_password_set;
00626     av_namespace_set(ent, stf);
00627 
00628     ent = av_namespace_lookup(ns, NULL, "loggedin");
00629     AV_NEW(stf);
00630     stf->data = &(davdat->sessions);
00631     stf->get = pass_loggedin_get;
00632     stf->set = pass_loggedin_set;
00633     av_namespace_set(ent, stf);
00634 
00635     av_unref_obj(ns);
00636 
00637     return 0;
00638 }
00639 
00640 /* ---------------------------------------------------------------------- */
00641 
00642 static void dav_destroy(struct remote *rem)
00643 {
00644     struct davdata *davdat = (struct davdata *) rem->data;
00645 
00646     av_free (davdat);
00647     av_free (rem->name);
00648     av_free (rem);
00649 }
00650 
00651 /* ---------------------------------------------------------------------- */
00652 
00653 int av_init_module_dav(struct vmodule *module)
00654 {
00655     int res;
00656     struct remote *rem;
00657     struct avfs *avfs;
00658     struct davdata *davdat;
00659 
00660     av_log(AVLOG_DEBUG, "DAV: initializing");
00661 
00662     sock_init();
00663 
00664     AV_NEW(davdat);
00665     memset (&(davdat->allconns), 0, sizeof(davdat->allconns));
00666 
00667     snprintf (AV_UserAgent, 127, "AVFSCoda/%d", AV_VER);
00668 
00669     AV_NEW(rem);
00670 
00671     rem->data    = davdat;
00672     rem->flags   = REM_DIR_ONLY;
00673     rem->name    = av_strdup("dav");
00674     rem->list    = dav_list;
00675     rem->get     = dav_get;
00676     rem->wait    = dav_wait;
00677     rem->destroy = dav_destroy;
00678 
00679     res = av_remote_init(module, rem, &avfs);
00680     if (res == 0) {
00681        res = dav_init_ctl (module, davdat);
00682        if (res < 0) {
00683            av_unref_obj(avfs);
00684        }
00685     }
00686 
00687     return 0;
00688 }
00689 
00690 // vim:sw=4: