/* pfs-dav.c: * **************************************************************** * Copyright (C) 2002, 2003 Scott Parish * Copyright (C) 2003 Tom Lord * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include #include "hackerlab/bugs/panic.h" #include "hackerlab/mem/alloc-limits.h" #include "hackerlab/mem/mem.h" #include "hackerlab/char/str.h" #include "hackerlab/fmt/cvt.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/mem/talloc.h" #include "hackerlab/vu/safe.h" #include "libfsutils/tmp-files.h" #include "libfsutils/file-contents.h" #include "libarch/authinfo.h" #include "libarch/archives.h" #include "libarch/debug.h" #include "libarch/pfs-dav.h" #include "po/gettext.h" #include #include #include #include #include #include #include #include #undef MIN #define MIN(A,B) ((A) < (B) ? (A) : (B)) #define LISTING_FILE ".listing" static const ne_propname ls_props[] = { { "DAV:", "getcontentlength" }, { "DAV:", "getlastmodified" }, { "DAV:", "resourcetype" }, { NULL } }; struct authinfo { char *username; char *password; char *machine; }; struct ls_data { char *files; char *uri; }; struct dav_is_dir_ls_data { char *uri; int is_dir; }; struct arch_pfs_dav_session { struct arch_pfs_session pfs; char * cwd; char * dav_scheme; char * dav_hostname; int dav_port; struct authinfo * proxy_auth; ne_session * sess; ne_server_capabilities sess_opts; }; /* __STDC__ prototypes for static functions */ static void fs_disconnect (struct arch_pfs_session **pfs); static t_uchar * pfs_file_contents (struct arch_pfs_session * p, t_uchar * path, int soft_errors); static rel_table pfs_directory_files (struct arch_pfs_session * p, t_uchar * path, int soft_errors); static int quick_file_exists (struct arch_pfs_session * p, t_uchar * path); static int pfs_file_exists (struct arch_pfs_session * p, t_uchar * path); static int pfs_get_file (struct arch_pfs_session * p, int out_fd, t_uchar * path, int soft_errors); static int pfs_put_file (struct arch_pfs_session * p, t_uchar * path, mode_t perms, int in_fd, int soft_errors); static int pfs_mkdir (struct arch_pfs_session * p, t_uchar * path, mode_t mode, int soft_errors); static int pfs_rename (struct arch_pfs_session * p, t_uchar ** errstr, t_uchar * from, t_uchar * to, int soft_errors); static int pfs_is_dir (struct arch_pfs_session * p, t_uchar * path); static int dav_is_dir (struct arch_pfs_dav_session * pfs, char * dir); static int pfs_rmdir (struct arch_pfs_session * p, t_uchar * path, int soft_errors); static int pfs_rm (struct arch_pfs_session * p, t_uchar * path, int soft_errors); static void dav_is_dir_results (void * userdata, const char * uri, const ne_prop_result_set * set); static int dav_client_cwd (struct arch_pfs_dav_session * pfs, t_uchar * path, int soft_errors); static int dav_client_auth (void * userdata, const char * realm, int attempt, char * username, char * password); static t_uchar * abs_path (t_uchar * cwd, t_uchar * path); static t_uchar * dirfold (t_uchar *dir); static void results (void * userdata, const char * uri, const ne_prop_result_set * set); struct arch_pfs_vtable dav_pfs_fns = { fs_disconnect, pfs_file_exists, pfs_is_dir, pfs_file_contents, pfs_get_file, pfs_directory_files, pfs_put_file, pfs_mkdir, pfs_rename, pfs_rmdir, pfs_rm, }; int arch_pfs_dav_supported_protocol (t_uchar * uri) { if (!str_cmp_prefix ("http:", uri) || !str_cmp_prefix ("https:", uri)) return 1; else return 0; } /* Display informations about the certificate, and accept it */ static int ssl_accept(void *userdata, int failures, const ne_ssl_certificate *c) { /* (trivial) code taken from cadaver.c */ char *tmp, from[NE_SSL_VDATELEN], to[NE_SSL_VDATELEN]; const char *ident; struct authinfo * auth = userdata; ident = ne_ssl_cert_identity(c); if (ident) debug (dbg_dav, 1, _("WARNING: Accepting untrusted server certificate for `%s':\n"), ident); else debug (dbg_dav, 1, _("WARNING: Accepting untrusted server certificate\n")); if (failures & NE_SSL_IDMISMATCH) { debug (dbg_dav,1, _("Certificate was issued to hostname `%s' instead of `%s'.\n" "This connection may be hijacked!\n"), ne_ssl_cert_identity(c), auth->machine); } #define PRINT_AND_FREE(str, dn) \ tmp = ne_ssl_readable_dname(dn); safe_printfmt(2, str, tmp); free(tmp) PRINT_AND_FREE("Issued to: %s\n", ne_ssl_cert_subject(c)); PRINT_AND_FREE("Issued by: %s\n", ne_ssl_cert_issuer(c)); ne_ssl_cert_validity(c, from, to); safe_printfmt(2, "Certificate is valid from %s to %s\n", from, to); return 0; } struct arch_pfs_session * arch_pfs_dav_connect (t_uchar * uri, int soft_errors) { int ign; struct arch_pfs_dav_session * answer = 0; t_uchar * host; t_uchar * user; t_uchar * passwd; t_uchar * hostname; t_uchar * portstr; t_uchar * root_path = 0; t_uchar * proxy; t_uchar * proxy_port; struct authinfo * auth = 0; int proxy_port_num; /* ne_sock_init() is idempotent, so just init always */ if (ne_sock_init ()) { if (soft_errors) return NULL; panic (_("arch_pfs_dav_connect: ne_sock_init() failed.")); } answer = (struct arch_pfs_dav_session *)lim_malloc (0, sizeof (*answer)); mem_set0 ((t_uchar *)answer, sizeof (*answer)); answer->proxy_auth = talloc (NULL, struct authinfo); answer->pfs.vtable = &dav_pfs_fns; if (!str_cmp_prefix ("http://", uri)) { answer->dav_scheme = str_save (0, "http"); host = str_save (0, uri + sizeof ("http://") - 1); } else if (!str_cmp_prefix ("https://", uri)) { answer->dav_scheme = str_save (0, "https"); host = str_save (0, uri + sizeof ("https://") - 1); } else { if (soft_errors) { lim_free (0, answer); return NULL; } panic (_("bogus uri to arch_pfs_dav_connect")); } root_path = str_chr_index (host, '/'); if (root_path) { t_uchar * pos; pos = root_path; root_path = str_save (0, root_path); *pos = 0; } hostname = str_chr_index (host, '@'); auth = lim_malloc (0, sizeof (struct authinfo)); if (!hostname) { answer->dav_hostname = str_save (0, host); auth->machine = str_save (0, host); auth->username = str_save(0, ""); auth->password = str_save(0, ""); } else { user = host; auth = lim_malloc (0, sizeof (struct authinfo)); passwd = str_chr_index_n (user, hostname - user, ':'); if (passwd++) { auth->username = str_save_n (0, user, (passwd - 1) - user); auth->password = str_save_n (0, passwd, hostname - passwd); } else { auth->username = str_save_n (0, user, hostname - user); auth->password = str_save (0, ""); } /* machine name will be useful to lookup in ~/.authinfo file */ auth->machine = str_save (0, hostname + 1); answer->dav_hostname = str_save (0, hostname + 1); } portstr = str_chr_index (answer->dav_hostname, ':'); if (portstr) *(portstr++) = 0; answer->dav_port = ne_uri_defaultport (answer->dav_scheme); if (portstr && (0 > cvt_decimal_to_uint (&ign, &answer->dav_port, portstr, str_length (portstr)))) { if (soft_errors) { lim_free (0, answer); /* FIXME free answers resources */ return NULL; } safe_printfmt (2, "illegal port number in uri -- %s\n", uri); exit (2); } answer->sess = ne_session_create (answer->dav_scheme, answer->dav_hostname, answer->dav_port); if (!str_cmp ("https", answer->dav_scheme)) { ne_ssl_trust_default_ca (answer->sess); /* accept SSL certificate anyway */ ne_ssl_set_verify(answer->sess, ssl_accept, auth); } ne_set_server_auth (answer->sess, dav_client_auth, auth); ne_set_useragent (answer->sess, "arch-client/0.1"); /* * Retrieve the proxy. Do not alter the environment variable. * The proxy variable is formatted as * scheme://host:port/ * We only support http as the scheme. */ proxy = getenv ("http_proxy"); if (proxy) proxy = str_save (0, proxy); else { proxy = getenv ("HTTP_PROXY"); if (proxy) proxy = str_save (0, proxy); } if (proxy && !str_cmp_prefix ("http://", proxy)) { t_uchar * proxy_path = proxy + 7; proxy_port = str_chr_index (proxy_path, ':'); if (proxy_port) { unsigned proxy_port_len; *proxy_port++ = 0; proxy_port_len = str_length (proxy_port); /* The port may be followed by a trailing slash (according to rcollins, it _must_ be followed by a slash, strictly speaking, but this restriction is widely ignored both by software and by users, so it's not practical to enforce it, and there's no real gain from doing so). */ if (proxy_port_len > 0 && proxy_port[proxy_port_len - 1] == '/') proxy_port_len--; if (cvt_decimal_to_int (&ign, &proxy_port_num, proxy_port, proxy_port_len)) { if (soft_errors) { lim_free (0, answer); return NULL; } safe_printfmt (2, "ill formated http proxy port number from $http_proxy\n"); exit (2); } ne_session_proxy (answer->sess, proxy_path, proxy_port_num); } answer->proxy_auth->machine = talloc_strdup(answer->proxy_auth, proxy_path); answer->proxy_auth->username = talloc_strdup(answer->proxy_auth, ""); answer->proxy_auth->password = talloc_strdup(answer->proxy_auth, ""); ne_set_proxy_auth(answer->sess, dav_client_auth, answer->proxy_auth); } if (root_path) { int result = dav_client_cwd (answer, root_path, soft_errors); if (result) { if (soft_errors) { lim_free (0, answer); return NULL; } panic (_("cannot change DAV cwd.")); } } lim_free (0, host); lim_free (0, root_path); lim_free (0, proxy); return (struct arch_pfs_session *)answer; } void fs_disconnect (struct arch_pfs_session **pfs) { struct arch_pfs_dav_session * local_pfs = (struct arch_pfs_dav_session *)*pfs; struct authinfo * proxy_auth = local_pfs->proxy_auth; talloc_free (proxy_auth); lim_free (0, *pfs); *pfs = NULL; } static t_uchar * pfs_file_contents (struct arch_pfs_session * p, t_uchar * path, int soft_errors) { struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *)p; t_uchar * tmp_path = 0; int fd; t_uchar * dav_path = 0; int ne_err; t_uchar * answer = 0; tmp_path = tmp_file_name_in_tmp (",,pfs-dav-file-contents"); fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0000); safe_unlink (tmp_path); dav_path = abs_path (pfs->cwd, (char *)path); ne_err = ne_get (pfs->sess, dav_path, fd); if (ne_err) { if (!soft_errors) { safe_printfmt (2, "webdav error: %s\n", ne_get_error (pfs->sess)); exit (2); } } else { safe_lseek (fd, (off_t)0, SEEK_SET); answer = fd_contents (fd); } safe_close (fd); lim_free (0, tmp_path); lim_free (0, dav_path); return answer; } static rel_table pfs_directory_files (struct arch_pfs_session * p, t_uchar * path, int soft_errors) { struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *)p; int ne_err; t_uchar * dir = 0; t_uchar * file_string = 0; rel_table answer = 0; if (pfs->sess_opts.dav_class1) { struct ls_data data; dir = abs_path (pfs->cwd, path); if (dir[str_length (dir) - 1] != '/') dir = str_realloc_cat(0, dir, "/"); data.files = str_save (0, "");; data.uri = dir; if ((ne_err = ne_simple_propfind (pfs->sess, dir, NE_DEPTH_ONE, ls_props, results, &data))) { if (!soft_errors) { safe_printfmt (2, "webdav error (directory_files): %s (%s)\n", dir, ne_get_error (pfs->sess)); exit (2); } } else { file_string = str_save (0, data.files); } lim_free (0, data.files); } else { dir = file_name_in_vicinity (0, path, LISTING_FILE); file_string = pfs_file_contents (p, dir, 1); } answer = rel_ws_split (file_string); lim_free (0, dir); lim_free (0, file_string); return answer; } static int quick_file_exists (struct arch_pfs_session * p, t_uchar * path) { int answer = -1; struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *) p; t_uchar * dav_path = abs_path (pfs->cwd, (char *) path); ne_request *req = ne_request_create (pfs -> sess, "HEAD", dav_path); int ret = ne_request_dispatch (req); if (ret == NE_OK) switch (ne_get_status (req) -> code) { case 200: answer = 1; break; case 404: answer = 0; break; default: break; }; ne_request_destroy(req); lim_free(0, dav_path); return answer; } static int pfs_file_exists (struct arch_pfs_session * p, t_uchar * path) { t_uchar * path_dir = 0; t_uchar * path_tail = 0; rel_table files = 0; int answer = -1; int x; if (0) answer = quick_file_exists (p, path); if (answer != -1) return answer; else answer = 0; /* There is probably a better way to do this in webdav. */ path_dir = file_name_directory_file (0, path); if (!path_dir) { path_dir = str_save (0, "."); } path_tail = file_name_tail (0, path); files = pfs_directory_files (p, path_dir, 1); for (x = 0; x < rel_n_records (files); ++x) { if (!str_cmp (path_tail, files[x][0])) { answer = 1; break; } } lim_free (0, path_dir); lim_free (0, path_tail); rel_free_table (files); return answer; } static int pfs_get_file (struct arch_pfs_session * p, int out_fd, t_uchar * path, int soft_errors) { struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *)p; t_uchar * dav_path = 0; int ne_err; int answer = 0; dav_path = abs_path (pfs->cwd, (char *)path); ne_err = ne_get (pfs->sess, dav_path, out_fd); if (ne_err) { if (!soft_errors) { safe_printfmt (2, "webdav error: %s\n", ne_get_error (pfs->sess)); exit (2); } else answer = -1; } lim_free (0, dav_path); return answer; } static int pfs_put_file (struct arch_pfs_session * p, t_uchar * path, mode_t perms, int in_fd, int soft_errors) { struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *)p; int ne_err; t_uchar * file; int answer = 0; file = abs_path (pfs->cwd, path); ne_err = ne_put (pfs->sess, file, in_fd); if (ne_err) { if (!soft_errors) { safe_printfmt (2, "webdav error(put_file): writing to %s (%s)\n", file, ne_get_error (pfs->sess)); exit (2); } else { answer = -1; } } lim_free (0, file); return answer; } static int pfs_mkdir (struct arch_pfs_session * p, t_uchar * path, mode_t mode, int soft_errors) { struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *)p; int ne_err; t_uchar * dir = 0; int answer = 0; dir = abs_path (pfs->cwd, path); if (dir[str_length (dir) - 1] != '/') dir = str_realloc_cat (0, dir, "/"); if ((ne_err = ne_mkcol (pfs->sess, dir))) { if (!soft_errors) { safe_printfmt (2, "webdav error (pfs_mkdir): %s (%s)\n", path, ne_get_error (pfs->sess)); exit (2); } else answer = -1; } lim_free (0, dir); return answer; } static int pfs_rename (struct arch_pfs_session * p, t_uchar ** errstr, t_uchar * from, t_uchar * to, int soft_errors) { struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *)p; int ne_err; int ret; t_uchar * from2; t_uchar * to2; int result = 0; from2 = abs_path (pfs->cwd, from); to2 = abs_path (pfs->cwd, to); ret = dav_is_dir (pfs, from2); if (ret == -1) { if (errstr) *errstr = str_save (0, "file does not exist for rename"); result = -1; } else { if (ret) { if (from2[str_length (from2) - 1] != '/') from2 = str_realloc_cat (0, from2, "/"); if (to2[str_length (to2) - 1] != '/') to2 = str_realloc_cat (0, to2, "/"); } if (0 < dav_is_dir (pfs, to2)) { char * b; char * file; b = file = str_save (0, from); filename: file = str_chr_rindex (file, '/'); if (!file) file = b; else if (!file[1]) { file[0] = 0; goto filename; } else file++; to2 = str_realloc_cat_many (0, to2, "/", file, str_end); lim_free (0, b); } if ((ne_err = ne_move (pfs->sess, 0, from2, to2))) { if (errstr) *errstr = str_save (0, ne_get_error (pfs->sess)); result = -1; } } lim_free (0, from2); lim_free (0, to2); return result; } static int pfs_is_dir (struct arch_pfs_session * p, t_uchar * path) { struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *)p; t_uchar * abs = 0; int answer; abs = abs_path (pfs->cwd, path); answer = dav_is_dir (pfs, abs); lim_free (0, abs); return answer; } static int dav_is_dir (struct arch_pfs_dav_session * pfs, char * dir) { char * filename; char * parent; struct dav_is_dir_ls_data data; int ne_err; int answer; parent = str_save (0, dir); filename = str_chr_rindex (parent, '/'); filename[1] = 0; data.uri = dir; data.is_dir = 0; if((ne_err = ne_simple_propfind (pfs->sess, parent, NE_DEPTH_ONE, ls_props, dav_is_dir_results, &data))) { answer = -1; } else { answer = data.is_dir; } lim_free (0, parent); return answer; } static int pfs_rmdir (struct arch_pfs_session * p, t_uchar * path, int soft_errors) { struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *)p; int ne_err; t_uchar * dir = 0; int answer = 0; dir = abs_path (pfs->cwd, path); if (dir[str_length (dir) - 1] != '/') dir = str_realloc_cat (0, dir, "/"); if ((ne_err = ne_delete (pfs->sess, dir))) { if (!soft_errors) { safe_printfmt (2, "webdav error (pfs_rmdir): %s (%s)\n", dir, ne_get_error (pfs->sess)); exit (2); } else answer = -1; } lim_free (0, dir); return answer; } static int pfs_rm (struct arch_pfs_session * p, t_uchar * path, int soft_errors) { struct arch_pfs_dav_session * pfs = (struct arch_pfs_dav_session *)p; int ne_err; t_uchar * abs = 0; int answer = 0; abs = abs_path (pfs->cwd, path); if ((ne_err = ne_delete (pfs->sess, abs))) { if (!soft_errors) { safe_printfmt (2, "webdav error (pfs_rm): %s (%s)\n", path, ne_get_error (pfs->sess)); exit (2); } else answer = -1; } lim_free (0, abs); return 0; } static void dav_is_dir_results (void * userdata, const char * uri, const ne_prop_result_set * set) { struct dav_is_dir_ls_data * data = (struct dav_is_dir_ls_data *)userdata; int len; len = str_length (data->uri); if (!str_cmp_n (uri, len, data->uri, len)) { if (uri[str_length (uri) - 1] == '/') data->is_dir = 1; else data->is_dir = 0; } } static int dav_client_cwd (struct arch_pfs_dav_session * pfs, t_uchar * path, int soft_errors) { int ne_err; t_uchar * tmp; t_uchar * dir; int result = 0; dir = abs_path (pfs->cwd, path); if (dir[str_length (dir) - 1] != '/') dir = str_realloc_cat (0, dir, "/"); if ((ne_err = ne_options (pfs->sess, dir, &pfs->sess_opts))) { /* if error assume that server doesn't implement OPTIONS */ pfs->sess_opts.dav_class1 = 0; pfs->sess_opts.dav_class2 = 0; pfs->sess_opts.dav_executable = 0; } if (pfs->sess_opts.dav_class1) /* if the server supports DAV */ { if ((ne_err = ne_simple_propfind (pfs->sess, dir, NE_DEPTH_ONE, ls_props, NULL, NULL))) { if (soft_errors) { result = -1; goto exit; } safe_printfmt (2, "webdav error: %s\n", ne_get_error (pfs->sess)); exit (2); } } else /* no dav, so look for a listing file */ { time_t mtime; char * dirlisting = 0; dirlisting = str_alloc_cat (0, dir, LISTING_FILE); if ((ne_err = ne_getmodtime (pfs->sess, dirlisting, &mtime))) { if (soft_errors) { result = -1; goto exit; } safe_printfmt (2, "unable to access URL: %s\nwebdav error: %s\n", dirlisting, ne_get_error (pfs->sess)); exit (2); } lim_free (0, dirlisting); } tmp = ne_path_unescape (dir); if (pfs->cwd) free (pfs->cwd); pfs->cwd = tmp; exit: lim_free (0, dir); return result; } static int dav_client_auth (void * userdata, const char * realm, int attempt, char * username, char * password) { struct authinfo *auth; int len; if (attempt > 1) return attempt; /* Since we got a void *, we need to cast it. This is an easy way * to do so. */ auth = userdata; len = str_length (auth->username); if (len) str_cpy_n (username, auth->username, MIN (len, NE_ABUFSIZ - 1)); else { /* User is not set. Try getting it from the authinfo file */ t_uchar * get_user = NULL; get_user = arch_auth_username (auth->machine); len = str_length(get_user); str_cpy_n (username, get_user, MIN (len, NE_ABUFSIZ - 1)); lim_free (0, get_user); } len = str_length (auth->password); if (len) str_cpy_n (password, auth->password, MIN (len, NE_ABUFSIZ - 1)); else { /* pass not set. try getting it from the authinfo file */ t_uchar * get_pass = NULL; get_pass = arch_auth_password (username, auth->machine); len = str_length(get_pass); str_cpy_n (password, get_pass, MIN (len, NE_ABUFSIZ - 1)); lim_free (0, get_pass); } return 0; } static t_uchar * abs_path (t_uchar * cwd, t_uchar * path) { t_uchar * file; t_uchar * tmp; if (cwd == NULL) cwd = "/"; if (path[0] != '/') file = str_alloc_cat (0, cwd, path); else file = str_save (0, path); dirfold (file); tmp = ne_path_escape (file); lim_free (0, file); file = str_save (0, tmp); free (tmp); return file; } static t_uchar * dirfold (t_uchar *dir) { t_uchar * buf; t_uchar * this; t_uchar * next; int dir_i = 0; this = next = buf = str_save (0, dir); while ((this = str_separate (&next, "/")) != NULL) { if (str_length (this) == 0 || (str_length (this) == 1 && this[0] == '.')) continue; else if (str_length (this) == 2 && *this == '.' && this[1] == '.') { if (dir_i > 0) dir_i = (int)(strrchr (dir, '/') - (char *)dir); dir[dir_i] = 0; } else { dir[dir_i++] = '/'; strcpy (dir + dir_i, this); dir_i += str_length (this); } } lim_free (0, buf); if (!str_length (dir)) str_cpy (dir, "/"); return dir; } static void results (void * userdata, const char * uri, const ne_prop_result_set * set) { int n; char * file, * tmp; struct ls_data * data = userdata; if (str_cmp (data->uri, uri)) { if (1 == (n = str_length (uri))) return; if (uri[n - 1] == '/') n--; file = str_chr_rindex_n (uri, n, '/') + 1; n = str_length (file); if (file[n - 1] == '/') n--; file = str_save_n (0, file, n); tmp = ne_path_unescape (file); lim_free (0, file); data->files = str_realloc_cat_many (0, data->files, tmp, "\r\n", str_end); free (tmp); } } /* tag: Tom Lord Thu Jun 5 15:23:06 2003 (pfs-dav.c) */