| /* |
| * |
| * OBEX Server |
| * |
| * Copyright (C) 2009-2010 Intel Corporation |
| * Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <dirent.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/statvfs.h> |
| #include <sys/sendfile.h> |
| #include <fcntl.h> |
| #include <sys/wait.h> |
| #include <inttypes.h> |
| |
| #include <glib.h> |
| |
| #include "obexd/src/obexd.h" |
| #include "obexd/src/plugin.h" |
| #include "obexd/src/log.h" |
| #include "obexd/src/mimetype.h" |
| #include "filesystem.h" |
| |
| #define EOL_CHARS "\n" |
| |
| #define FL_VERSION "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" EOL_CHARS |
| |
| #define FL_TYPE "<!DOCTYPE folder-listing SYSTEM \"obex-folder-listing.dtd\">" EOL_CHARS |
| |
| #define FL_TYPE_PCSUITE "<!DOCTYPE folder-listing SYSTEM \"obex-folder-listing.dtd\"" EOL_CHARS \ |
| " [ <!ATTLIST folder mem-type CDATA #IMPLIED> ]>" EOL_CHARS |
| |
| #define FL_BODY_BEGIN "<folder-listing version=\"1.0\">" EOL_CHARS |
| |
| #define FL_BODY_END "</folder-listing>" EOL_CHARS |
| |
| #define FL_PARENT_FOLDER_ELEMENT "<parent-folder/>" EOL_CHARS |
| |
| #define FL_FILE_ELEMENT "<file name=\"%s\" size=\"%" PRIu64 "\"" \ |
| " %s accessed=\"%s\" " \ |
| "modified=\"%s\" created=\"%s\"/>" EOL_CHARS |
| |
| #define FL_FOLDER_ELEMENT "<folder name=\"%s\" %s accessed=\"%s\" " \ |
| "modified=\"%s\" created=\"%s\"/>" EOL_CHARS |
| |
| #define FL_FOLDER_ELEMENT_PCSUITE "<folder name=\"%s\" %s accessed=\"%s\"" \ |
| " modified=\"%s\" mem-type=\"DEV\"" \ |
| " created=\"%s\"/>" EOL_CHARS |
| |
| #define FTP_TARGET_SIZE 16 |
| |
| static const uint8_t FTP_TARGET[FTP_TARGET_SIZE] = { |
| 0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, |
| 0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09 }; |
| |
| #define PCSUITE_WHO_SIZE 8 |
| |
| static const uint8_t PCSUITE_WHO[PCSUITE_WHO_SIZE] = { |
| 'P', 'C', ' ', 'S', 'u', 'i', 't', 'e' }; |
| |
| gboolean is_filename(const char *name) |
| { |
| if (strchr(name, '/')) |
| return FALSE; |
| |
| if (strcmp(name, ".") == 0) |
| return FALSE; |
| |
| if (strcmp(name, "..") == 0) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| int verify_path(const char *path) |
| { |
| char *t; |
| int ret = 0; |
| |
| if (obex_option_symlinks()) |
| return 0; |
| |
| t = realpath(path, NULL); |
| if (t == NULL) |
| return -errno; |
| |
| if (!g_str_has_prefix(t, obex_option_root_folder())) |
| ret = -EPERM; |
| |
| free(t); |
| |
| return ret; |
| } |
| |
| static char *file_stat_line(char *filename, struct stat *fstat, |
| struct stat *dstat, gboolean root, |
| gboolean pcsuite) |
| { |
| char perm[51], atime[18], ctime[18], mtime[18]; |
| char *escaped, *ret = NULL; |
| |
| snprintf(perm, 50, "user-perm=\"%s%s%s\" group-perm=\"%s%s%s\" " |
| "other-perm=\"%s%s%s\"", |
| (fstat->st_mode & S_IRUSR ? "R" : ""), |
| (fstat->st_mode & S_IWUSR ? "W" : ""), |
| (dstat->st_mode & S_IWUSR ? "D" : ""), |
| (fstat->st_mode & S_IRGRP ? "R" : ""), |
| (fstat->st_mode & S_IWGRP ? "W" : ""), |
| (dstat->st_mode & S_IWGRP ? "D" : ""), |
| (fstat->st_mode & S_IROTH ? "R" : ""), |
| (fstat->st_mode & S_IWOTH ? "W" : ""), |
| (dstat->st_mode & S_IWOTH ? "D" : "")); |
| |
| strftime(atime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_atime)); |
| strftime(ctime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_ctime)); |
| strftime(mtime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_mtime)); |
| |
| escaped = g_markup_escape_text(filename, -1); |
| |
| if (S_ISDIR(fstat->st_mode)) { |
| if (pcsuite && root && g_str_equal(filename, "Data")) |
| ret = g_strdup_printf(FL_FOLDER_ELEMENT_PCSUITE, |
| escaped, perm, atime, |
| mtime, ctime); |
| else |
| ret = g_strdup_printf(FL_FOLDER_ELEMENT, escaped, perm, |
| atime, mtime, ctime); |
| } else if (S_ISREG(fstat->st_mode)) |
| ret = g_strdup_printf(FL_FILE_ELEMENT, escaped, |
| (uint64_t) fstat->st_size, |
| perm, atime, mtime, ctime); |
| |
| g_free(escaped); |
| |
| return ret; |
| } |
| |
| static void *filesystem_open(const char *name, int oflag, mode_t mode, |
| void *context, size_t *size, int *err) |
| { |
| struct stat stats; |
| struct statvfs buf; |
| int fd, ret; |
| uint64_t avail; |
| |
| fd = open(name, oflag, mode); |
| if (fd < 0) { |
| if (err) |
| *err = -errno; |
| return NULL; |
| } |
| |
| if (fstat(fd, &stats) < 0) { |
| if (err) |
| *err = -errno; |
| goto failed; |
| } |
| |
| ret = verify_path(name); |
| if (ret < 0) { |
| if (err) |
| *err = ret; |
| goto failed; |
| } |
| |
| if (oflag == O_RDONLY) { |
| if (size) |
| *size = stats.st_size; |
| goto done; |
| } |
| |
| if (fstatvfs(fd, &buf) < 0) { |
| if (err) |
| *err = -errno; |
| goto failed; |
| } |
| |
| if (size == NULL) |
| goto done; |
| |
| avail = (uint64_t) buf.f_bsize * buf.f_bavail; |
| if (avail < *size) { |
| if (err) |
| *err = -ENOSPC; |
| goto failed; |
| } |
| |
| done: |
| if (err) |
| *err = 0; |
| |
| return GINT_TO_POINTER(fd); |
| |
| failed: |
| close(fd); |
| return NULL; |
| } |
| |
| static int filesystem_close(void *object) |
| { |
| if (close(GPOINTER_TO_INT(object)) < 0) |
| return -errno; |
| |
| return 0; |
| } |
| |
| static ssize_t filesystem_read(void *object, void *buf, size_t count) |
| { |
| ssize_t ret; |
| |
| ret = read(GPOINTER_TO_INT(object), buf, count); |
| if (ret < 0) |
| return -errno; |
| |
| return ret; |
| } |
| |
| static ssize_t filesystem_write(void *object, const void *buf, size_t count) |
| { |
| ssize_t ret; |
| |
| ret = write(GPOINTER_TO_INT(object), buf, count); |
| if (ret < 0) |
| return -errno; |
| |
| return ret; |
| } |
| |
| static int filesystem_rename(const char *name, const char *destname) |
| { |
| int ret; |
| |
| ret = rename(name, destname); |
| if (ret < 0) { |
| error("rename(%s, %s): %s (%d)", name, destname, |
| strerror(errno), errno); |
| return -errno; |
| } |
| |
| return ret; |
| } |
| |
| static int sendfile_async(int out_fd, int in_fd, off_t *offset, size_t count) |
| { |
| int pid; |
| |
| /* Run sendfile on child process */ |
| pid = fork(); |
| switch (pid) { |
| case 0: |
| break; |
| case -1: |
| error("fork() %s (%d)", strerror(errno), errno); |
| return -errno; |
| default: |
| DBG("child %d forked", pid); |
| return pid; |
| } |
| |
| /* At child */ |
| if (sendfile(out_fd, in_fd, offset, count) < 0) |
| error("sendfile(): %s (%d)", strerror(errno), errno); |
| |
| close(in_fd); |
| close(out_fd); |
| |
| exit(errno); |
| } |
| |
| static int filesystem_copy(const char *name, const char *destname) |
| { |
| void *in, *out; |
| ssize_t ret; |
| size_t size; |
| struct stat st; |
| int in_fd, out_fd, err; |
| |
| in = filesystem_open(name, O_RDONLY, 0, NULL, &size, &err); |
| if (in == NULL) { |
| error("open(%s): %s (%d)", name, strerror(-err), -err); |
| return -err; |
| } |
| |
| in_fd = GPOINTER_TO_INT(in); |
| ret = fstat(in_fd, &st); |
| if (ret < 0) { |
| error("stat(%s): %s (%d)", name, strerror(errno), errno); |
| return -errno; |
| } |
| |
| out = filesystem_open(destname, O_WRONLY | O_CREAT | O_TRUNC, |
| st.st_mode, NULL, &size, &err); |
| if (out == NULL) { |
| error("open(%s): %s (%d)", destname, strerror(-err), -err); |
| filesystem_close(in); |
| return -errno; |
| } |
| |
| out_fd = GPOINTER_TO_INT(out); |
| |
| /* Check if sendfile is supported */ |
| ret = sendfile(out_fd, in_fd, NULL, 0); |
| if (ret < 0) { |
| ret = -errno; |
| error("sendfile: %s (%zd)", strerror(-ret), -ret); |
| goto done; |
| } |
| |
| ret = sendfile_async(out_fd, in_fd, NULL, st.st_size); |
| if (ret < 0) |
| goto done; |
| |
| return 0; |
| |
| done: |
| filesystem_close(in); |
| filesystem_close(out); |
| |
| return ret; |
| } |
| |
| struct capability_object { |
| int pid; |
| int output; |
| int err; |
| gboolean aborted; |
| GString *buffer; |
| }; |
| |
| static void script_exited(GPid pid, int status, void *data) |
| { |
| struct capability_object *object = data; |
| char buf[128]; |
| |
| object->pid = -1; |
| |
| DBG("pid: %d status: %d", pid, status); |
| |
| g_spawn_close_pid(pid); |
| |
| /* free the object if aborted */ |
| if (object->aborted) { |
| if (object->buffer != NULL) |
| g_string_free(object->buffer, TRUE); |
| |
| g_free(object); |
| return; |
| } |
| |
| if (WEXITSTATUS(status) != EXIT_SUCCESS) { |
| memset(buf, 0, sizeof(buf)); |
| if (read(object->err, buf, sizeof(buf)) > 0) |
| error("%s", buf); |
| obex_object_set_io_flags(data, G_IO_ERR, -EPERM); |
| } else |
| obex_object_set_io_flags(data, G_IO_IN, 0); |
| } |
| |
| static int capability_exec(const char **argv, int *output, int *err) |
| { |
| GError *gerr = NULL; |
| int pid; |
| GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH; |
| |
| if (!g_spawn_async_with_pipes(NULL, (char **) argv, NULL, flags, NULL, |
| NULL, &pid, NULL, output, err, &gerr)) { |
| error("%s", gerr->message); |
| g_error_free(gerr); |
| return -EPERM; |
| } |
| |
| DBG("executing %s pid %d", argv[0], pid); |
| |
| return pid; |
| } |
| |
| static void *capability_open(const char *name, int oflag, mode_t mode, |
| void *context, size_t *size, int *err) |
| { |
| struct capability_object *object = NULL; |
| char *buf; |
| const char *argv[2]; |
| |
| if (oflag != O_RDONLY) |
| goto fail; |
| |
| object = g_new0(struct capability_object, 1); |
| object->pid = -1; |
| object->output = -1; |
| object->err = -1; |
| |
| if (name[0] != '!') { |
| GError *gerr = NULL; |
| gboolean ret; |
| |
| ret = g_file_get_contents(name, &buf, NULL, &gerr); |
| if (ret == FALSE) { |
| error("%s", gerr->message); |
| g_error_free(gerr); |
| goto fail; |
| } |
| |
| object->buffer = g_string_new(buf); |
| |
| if (size) |
| *size = object->buffer->len; |
| |
| goto done; |
| } |
| |
| argv[0] = &name[1]; |
| argv[1] = NULL; |
| |
| object->pid = capability_exec(argv, &object->output, &object->err); |
| if (object->pid < 0) |
| goto fail; |
| |
| /* Watch cannot be removed while the process is still running */ |
| g_child_watch_add(object->pid, script_exited, object); |
| |
| done: |
| if (err) |
| *err = 0; |
| |
| return object; |
| |
| fail: |
| if (err) |
| *err = -EPERM; |
| |
| g_free(object); |
| return NULL; |
| } |
| |
| static GString *append_pcsuite_preamble(GString *object) |
| { |
| return g_string_append(object, FL_TYPE_PCSUITE); |
| } |
| |
| static GString *append_folder_preamble(GString *object) |
| { |
| return g_string_append(object, FL_TYPE); |
| } |
| |
| static GString *append_listing(GString *object, const char *name, |
| gboolean pcsuite, size_t *size, int *err) |
| { |
| struct stat fstat, dstat; |
| struct dirent *ep; |
| DIR *dp; |
| gboolean root; |
| int ret; |
| |
| root = g_str_equal(name, obex_option_root_folder()); |
| |
| dp = opendir(name); |
| if (dp == NULL) { |
| if (err) |
| *err = -ENOENT; |
| goto failed; |
| } |
| |
| if (!root) |
| object = g_string_append(object, FL_PARENT_FOLDER_ELEMENT); |
| |
| ret = verify_path(name); |
| if (ret < 0) { |
| *err = ret; |
| goto failed; |
| } |
| |
| ret = stat(name, &dstat); |
| if (ret < 0) { |
| if (err) |
| *err = -errno; |
| goto failed; |
| } |
| |
| while ((ep = readdir(dp))) { |
| char *filename; |
| char *fullname; |
| char *line; |
| |
| if (ep->d_name[0] == '.') |
| continue; |
| |
| filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL); |
| if (filename == NULL) { |
| error("g_filename_to_utf8: invalid filename"); |
| continue; |
| } |
| |
| fullname = g_build_filename(name, ep->d_name, NULL); |
| |
| ret = stat(fullname, &fstat); |
| if (ret < 0) { |
| DBG("stat: %s(%d)", strerror(errno), errno); |
| g_free(filename); |
| g_free(fullname); |
| continue; |
| } |
| |
| g_free(fullname); |
| |
| line = file_stat_line(filename, &fstat, &dstat, root, FALSE); |
| if (line == NULL) { |
| g_free(filename); |
| continue; |
| } |
| |
| g_free(filename); |
| |
| object = g_string_append(object, line); |
| g_free(line); |
| } |
| |
| closedir(dp); |
| |
| object = g_string_append(object, FL_BODY_END); |
| if (size) |
| *size = object->len; |
| |
| if (err) |
| *err = 0; |
| |
| return object; |
| |
| failed: |
| if (dp) |
| closedir(dp); |
| |
| g_string_free(object, TRUE); |
| return NULL; |
| } |
| |
| static void *folder_open(const char *name, int oflag, mode_t mode, |
| void *context, size_t *size, int *err) |
| { |
| GString *object; |
| |
| object = g_string_new(FL_VERSION); |
| object = append_folder_preamble(object); |
| object = g_string_append(object, FL_BODY_BEGIN); |
| |
| return append_listing(object, name, FALSE, size, err); |
| } |
| |
| static void *pcsuite_open(const char *name, int oflag, mode_t mode, |
| void *context, size_t *size, int *err) |
| { |
| GString *object; |
| |
| object = g_string_new(FL_VERSION); |
| object = append_pcsuite_preamble(object); |
| object = g_string_append(object, FL_BODY_BEGIN); |
| |
| return append_listing(object, name, TRUE, size, err); |
| } |
| |
| static int string_free(void *object) |
| { |
| GString *string = object; |
| |
| g_string_free(string, TRUE); |
| |
| return 0; |
| } |
| |
| ssize_t string_read(void *object, void *buf, size_t count) |
| { |
| GString *string = object; |
| ssize_t len; |
| |
| if (string->len == 0) |
| return 0; |
| |
| len = MIN(string->len, count); |
| memcpy(buf, string->str, len); |
| g_string_erase(string, 0, len); |
| |
| return len; |
| } |
| |
| static ssize_t folder_read(void *object, void *buf, size_t count) |
| { |
| return string_read(object, buf, count); |
| } |
| |
| static ssize_t capability_read(void *object, void *buf, size_t count) |
| { |
| struct capability_object *obj = object; |
| |
| if (obj->buffer) |
| return string_read(obj->buffer, buf, count); |
| |
| if (obj->pid >= 0) |
| return -EAGAIN; |
| |
| return read(obj->output, buf, count); |
| } |
| |
| static int capability_close(void *object) |
| { |
| struct capability_object *obj = object; |
| int err = 0; |
| |
| if (obj->pid < 0) |
| goto done; |
| |
| DBG("kill: pid %d", obj->pid); |
| err = kill(obj->pid, SIGTERM); |
| if (err < 0) { |
| err = -errno; |
| error("kill: %s (%d)", strerror(-err), -err); |
| goto done; |
| } |
| |
| obj->aborted = TRUE; |
| return 0; |
| |
| done: |
| if (obj->buffer != NULL) |
| g_string_free(obj->buffer, TRUE); |
| |
| g_free(obj); |
| |
| return err; |
| } |
| |
| static struct obex_mime_type_driver file = { |
| .open = filesystem_open, |
| .close = filesystem_close, |
| .read = filesystem_read, |
| .write = filesystem_write, |
| .remove = remove, |
| .move = filesystem_rename, |
| .copy = filesystem_copy, |
| }; |
| |
| static struct obex_mime_type_driver capability = { |
| .target = FTP_TARGET, |
| .target_size = FTP_TARGET_SIZE, |
| .mimetype = "x-obex/capability", |
| .open = capability_open, |
| .close = capability_close, |
| .read = capability_read, |
| }; |
| |
| static struct obex_mime_type_driver folder = { |
| .target = FTP_TARGET, |
| .target_size = FTP_TARGET_SIZE, |
| .mimetype = "x-obex/folder-listing", |
| .open = folder_open, |
| .close = string_free, |
| .read = folder_read, |
| }; |
| |
| static struct obex_mime_type_driver pcsuite = { |
| .target = FTP_TARGET, |
| .target_size = FTP_TARGET_SIZE, |
| .who = PCSUITE_WHO, |
| .who_size = PCSUITE_WHO_SIZE, |
| .mimetype = "x-obex/folder-listing", |
| .open = pcsuite_open, |
| .close = string_free, |
| .read = folder_read, |
| }; |
| |
| static int filesystem_init(void) |
| { |
| int err; |
| |
| err = obex_mime_type_driver_register(&folder); |
| if (err < 0) |
| return err; |
| |
| err = obex_mime_type_driver_register(&capability); |
| if (err < 0) |
| return err; |
| |
| err = obex_mime_type_driver_register(&pcsuite); |
| if (err < 0) |
| return err; |
| |
| return obex_mime_type_driver_register(&file); |
| } |
| |
| static void filesystem_exit(void) |
| { |
| obex_mime_type_driver_unregister(&folder); |
| obex_mime_type_driver_unregister(&capability); |
| obex_mime_type_driver_unregister(&file); |
| } |
| |
| OBEX_PLUGIN_DEFINE(filesystem, filesystem_init, filesystem_exit) |