bool server_process_request(twopence_transaction_t *trans, twopence_buf_t *payload) { twopence_file_xfer_t xfer; twopence_command_t cmd; switch (trans->type) { case TWOPENCE_PROTO_TYPE_INJECT: twopence_file_xfer_init(&xfer); if (!twopence_protocol_dissect_inject_packet(payload, &xfer)) goto bad_packet; server_inject_file(trans, &xfer); twopence_file_xfer_destroy(&xfer); break; case TWOPENCE_PROTO_TYPE_EXTRACT: twopence_file_xfer_init(&xfer); if (!twopence_protocol_dissect_extract_packet(payload, &xfer)) goto bad_packet; server_extract_file(trans, &xfer); twopence_file_xfer_destroy(&xfer); break; case TWOPENCE_PROTO_TYPE_COMMAND: memset(&cmd, 0, sizeof(cmd)); if (!twopence_protocol_dissect_command_packet(payload, &cmd) || cmd.command[0] == '\0') goto bad_packet; server_run_command(trans, &cmd); twopence_command_destroy(&cmd); break; case TWOPENCE_PROTO_TYPE_QUIT: server_request_quit(); /* we should not get here */ trans->done = true; break; default: twopence_log_error("Unknown command code '%c' in global context\n", trans->type); return false; } return true; bad_packet: twopence_log_error("unable to parse %s packet", twopence_protocol_packet_type_to_string(trans->type)); return false; }
long server_file_size(const char *filename, int fd, int *status) { struct stat stb; if (fstat(fd, &stb) < 0) { *status = errno; twopence_log_error("%s: unable to stat: %m\n", filename); return -1; } if (!S_ISREG(stb.st_mode)) { twopence_log_error("%s: not a regular file\n", filename); *status = EISDIR; return -1; } return stb.st_size; }
static void server_restore_privileges(struct saved_ids *saved_ids) { if (saved_ids->uid == -1) return; seteuid(saved_ids->uid); if (geteuid() != saved_ids->uid) { twopence_log_error("Unable to restore previous uid %u: abort\n", saved_ids->uid); abort(); } setegid(saved_ids->gid); if (getegid() != saved_ids->gid) { twopence_log_error("Unable to restore previous gid %u: abort\n", saved_ids->gid); abort(); } }
static int __socket_queue_xmit(twopence_sock_t *sock, twopence_buf_t *bp, int flags) { int n = 0, f; f = fcntl(sock->fd, F_GETFL); if (flags & TWOPENCE_SOCK_XMIT_SYNCHRONOUS) fcntl(sock->fd, F_SETFL, f & ~O_NONBLOCK); if (sock->write_eof) { twopence_log_error("%s: attempt to queue data after write shutdown", __func__); goto out_drop_buffer; } if (flags & TWOPENCE_SOCK_XMIT_SYNCHRONOUS) { /* Flush out all queued packets first */ while (twopence_queue_head(&sock->xmit_queue) != NULL) { n = twopence_sock_send_queued(sock); if (n < 0) goto out_drop_buffer; } } /* If nothing is queued to the socket, we might as well try to * send this data directly. */ if (twopence_queue_empty(&sock->xmit_queue)) { if (flags & TWOPENCE_SOCK_XMIT_SYNCHRONOUS) { /* fully synchronous */ while (twopence_buf_count(bp) != 0) { n = twopence_sock_send_buffer(sock, bp); if (n < 0) goto out_drop_buffer; } } else if (flags & TWOPENCE_SOCK_XMIT_TRYTOWRITE) { /* opportunistic - write some */ (void) twopence_sock_send_buffer(sock, bp); } } /* If there's data left in this buffer, queue it to the socket */ if (twopence_buf_count(bp) != 0) { if (flags & TWOPENCE_SOCK_XMIT_CLONEBUF) bp = twopence_buf_clone(bp); twopence_queue_append(&sock->xmit_queue, twopence_packet_new(bp)); goto out; } out_drop_buffer: if (!(flags & TWOPENCE_SOCK_XMIT_CLONEBUF)) twopence_buf_free(bp); out: fcntl(sock->fd, F_SETFL, f); return n; }
static bool server_change_hats_permanently(const struct passwd *user, int *status) { /* Do nothing for the root user */ if (!strcmp(user->pw_name, "root")) return true; if (initgroups(user->pw_name, user->pw_gid) < 0 || setgid(user->pw_gid) < 0 || setuid(user->pw_uid) < 0) { *status = errno; twopence_log_error("Unable to drop privileges to become user %s: %m", user->pw_name); return false; } return true; }
static bool server_change_to_home(const struct passwd *user) { const char *homedir; if ((homedir = user->pw_dir) == NULL || homedir[0] != '/') { twopence_debug("user %s has a home directory of \"%s\", substituting \"/\"", user->pw_name, user->pw_dir); homedir = "/"; } if (chdir(homedir) < 0) { twopence_log_error("Cannot change to user %s's home directory: chdir(%s) failed: %m", user->pw_name, user->pw_dir); return false; } return true; }
twopence_sock_t * twopence_sock_accept(twopence_sock_t *sock) { int sock_fd; if (sock->fd < 0) return NULL; if (sock->poll_data == NULL || !(sock->poll_data->events & POLLIN)) return NULL; sock_fd = accept(sock->fd, NULL, NULL); if (sock_fd < 0) { if (errno != EAGAIN) twopence_log_error("failed to accept incoming connection on socket: %m"); return NULL; } return twopence_sock_new(sock_fd); }
static twopence_sock_t * __twopence_socket_new(int fd, int oflags) { twopence_sock_t *sock; int f; sock = twopence_calloc(1, sizeof(*sock)); sock->fd = fd; sock->closeit = true; /* Set flags (usually for nonblocking IO) */ if ((f = fcntl(fd, F_GETFL)) < 0 || fcntl(fd, F_SETFL, f | oflags) < 0) { twopence_log_error("socket_new: trouble setting socket to nonblocking I/O: %m\n"); /* Continue anyway */ } twopence_queue_init(&sock->xmit_queue); return sock; }
static bool server_change_hats_temporarily(const struct passwd *user, struct saved_ids *saved_ids, int *status) { /* Do nothing for the root user */ if (!strcmp(user->pw_name, "root")) { saved_ids->uid = -1; return true; } saved_ids->uid = getuid(); saved_ids->gid = getgid(); if (initgroups(user->pw_name, user->pw_gid) < 0 || setegid(user->pw_gid) < 0 || seteuid(user->pw_uid) < 0) { *status = errno; twopence_log_error("Unable to drop privileges to become user %s: %m", user->pw_name); server_restore_privileges(saved_ids); return false; } return true; }
bool server_run_command_recv(twopence_transaction_t *trans, const twopence_hdr_t *hdr, twopence_buf_t *payload) { switch (hdr->type) { case TWOPENCE_PROTO_TYPE_INTR: /* Send signal to process, and shut down all I/O. * When we send a signal, we're not really interested in what * it has to say, not even "aargh". */ if (trans->pid && !trans->done) { /* Send the KILL signal to all processes in the process group */ kill(-trans->pid, SIGKILL); twopence_transaction_close_sink(trans, 0); twopence_transaction_close_source(trans, 0); /* ID zero means all */ } break; default: twopence_log_error("Unknown command code '%c' in transaction context\n", hdr->type); break; } return true; }
/* * Open the TCP socket * * Returns the file descriptor if successful, or -1 if failed */ static twopence_sock_t * __twopence_tcp_open(struct twopence_pipe_target *pipe_handle) { struct twopence_tcp_target *handle = (struct twopence_tcp_target *) pipe_handle; char *copy, *hostname, *portname = NULL; struct addrinfo hints; struct addrinfo *ai_list, *ai; int socket_fd = -1; int res; copy = hostname = twopence_strdup(handle->server_spec); if (hostname[0] == '[') { /* Something like [::1] */ char *s; for (s = ++hostname; *s != ']'; ++s) { if (*s == '\0') { twopence_log_error("tcp: cannot parse \"%s\"", handle->server_spec); free(copy); return NULL; } } *s++ = '\0'; if (*s == ':') portname = ++s; /* Any other garbage is silently ignored for now */ } else if ((portname = strchr(hostname, ':')) != NULL) *portname++ = '\0'; if (portname == NULL) portname = TWOPENCE_TCP_PORT_DEFAULT_STR; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; res = getaddrinfo(hostname, portname, &hints, &ai_list); free(copy); copy = hostname = portname = NULL; if (res != 0) { twopence_log_error("tcp: cannot resolve \"%s\": %s", handle->server_spec, gai_strerror(res)); return NULL; } twopence_debug("trying to open connection to %s", handle->server_spec); for (ai = ai_list; ai && socket_fd < 0; ai = ai->ai_next) { socket_fd = socket(ai->ai_family, SOCK_STREAM, 0); if (socket_fd <= 0) break; // Open the connection if (connect(socket_fd, ai->ai_addr, ai->ai_addrlen) < 0) { /* Okay, this address didn't work. Try the next one */ close(socket_fd); socket_fd = -1; } } freeaddrinfo(ai_list); if (socket_fd <= 0) return NULL; /* Note, we do not pass O_NONBLOCK here, but we do set O_CLOEXEC */ return twopence_sock_new_flags(socket_fd, O_RDWR | O_CLOEXEC); }
int server_open_file_as(const char *username, const char *filename, unsigned int filemode, int oflags, int *status) { struct stat stb; struct saved_ids saved_ids; struct passwd *user; int fd; if (!(user = server_get_user(username, status))) { twopence_debug("Unknown user \"%s\"\n", username); return -1; } /* If the path is not absolute, interpret it relatively to the * user's home directory */ if (filename[0] != '/') { filename = server_build_path(user->pw_dir, filename); if (filename == NULL) { twopence_log_error("Unable to build path from user %s's home \"%s\" and relative name \"%s\"\n", username, user->pw_dir, filename); *status = ENAMETOOLONG; return false; } } twopence_debug("%s(user=%s, file=%s, flags=0%0)\n", __func__, username, filename, oflags); /* We may want to have the client specify the file mode as well */ if (!strcmp(username, "root")) { fd = open(filename, oflags, filemode); if (fd < 0) *status = errno; } else { if (!server_change_hats_temporarily(user, &saved_ids, status)) return -1; fd = open(filename, oflags, filemode); if (fd < 0) *status = errno; server_restore_privileges(&saved_ids); } if (fd < 0) return -1; if (fstat(fd, &stb) < 0) { *status = errno; twopence_log_error("failed to stat \"%s\": %m", filename); close(fd); return -1; } if (!S_ISREG(stb.st_mode)) { twopence_log_error("%s: not a regular file\n", filename); *status = EISDIR; close(fd); return -1; } if (oflags != O_RDONLY && fchmod(fd, filemode) < 0) { *status = errno; twopence_log_error("failed to change file mode \"%s\" to 0%o: %m", filename, filemode); close(fd); return -1; } return fd; }