lewdfingerd/lewdfingerd.c

368 lines
7.7 KiB
C
Raw Normal View History

2018-07-03 17:26:06 +00:00
#define _GNU_SOURCE
2018-07-16 18:52:44 +00:00
#define _BSD_SOURCE
#define _POSIX_C_SOURCE 200809L
2018-07-16 18:09:24 +00:00
#include <errno.h>
2018-07-03 17:26:06 +00:00
#include <netdb.h>
#include <poll.h>
2018-07-16 18:52:44 +00:00
#include <pwd.h>
#include <signal.h>
2018-07-03 17:26:06 +00:00
#include <stdarg.h>
2018-07-22 10:28:40 +00:00
#include <stdbool.h>
2018-07-03 17:26:06 +00:00
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
2018-07-03 17:26:06 +00:00
#include <unistd.h>
struct pollfd *listens = NULL;
size_t num_listens = 0;
void log_error(const char * restrict format, ...) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
2018-07-11 14:13:24 +00:00
void log_perror(const char *string) {
perror(string);
}
2018-07-03 17:26:06 +00:00
void setup_listen(void) {
struct addrinfo hints;
struct addrinfo *getaddrinfo_result;
// AF_UNSPEC: either IPv4 or IPv6
// SOCK_STREAM: TCP
// AI_PASSIVE: fill out my IP for me
// AI_ADDRCONFIG: only return addresses I have a configured interface for
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
int status = getaddrinfo(NULL, "79", &hints, &getaddrinfo_result);
if(status != 0) {
log_error("getaddrinfo failed: %s\n", gai_strerror(status));
exit(1);
}
for(struct addrinfo *res = getaddrinfo_result; res != NULL; res = res->ai_next) {
int yes = 1;
// Add corresponding interface to table of sockets
// Create socket
int sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(sock == -1) {
2018-07-11 14:13:24 +00:00
log_perror("socket");
2018-07-03 17:26:06 +00:00
exit(1);
}
// Disable the IPv4 over IPv6, as that results in IPv4 and IPv6 sockets conflicting and one of them not being able to be set up
if(res->ai_family == AF_INET6) {
if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) == -1) {
2018-07-11 14:13:24 +00:00
log_perror("setsockopt");
2018-07-03 17:26:06 +00:00
exit(1);
}
}
// Set reuseaddr
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
2018-07-11 14:13:24 +00:00
log_perror("setsockopt");
2018-07-03 17:26:06 +00:00
exit(1);
}
// Bind onto given address
if(bind(sock, res->ai_addr, res->ai_addrlen) == -1) {
2018-07-11 14:13:24 +00:00
log_perror("bind");
2018-07-03 17:26:06 +00:00
exit(1);
}
// Listen for incoming connections
if(listen(sock, 1) == -1) {
2018-07-11 14:13:24 +00:00
log_perror("listen");
2018-07-03 17:26:06 +00:00
exit(1);
}
// Grow the table of sockets
size_t index = num_listens++;
if(SIZE_MAX / sizeof(struct pollfd) < num_listens){
log_error("Too many interfaces to listen on\n");
exit(1);
}
listens = realloc(listens, num_listens * sizeof(struct pollfd));
if(listens == NULL) {
2018-07-11 14:13:24 +00:00
log_perror("realloc");
2018-07-03 17:26:06 +00:00
exit(1);
}
// Add socket to the table
struct pollfd new_pollobj = {.fd = sock, .events = POLLIN};
listens[index] = new_pollobj;
}
freeaddrinfo(getaddrinfo_result);
}
void drop_privileges(void) {
uid_t uid = getuid();
gid_t gid = getgid();
if(setresgid(gid, gid, gid) != 0) {
2018-07-11 14:13:24 +00:00
log_perror("setresgid");
2018-07-03 17:26:06 +00:00
exit(1);
}
if(setresuid(uid, uid, uid) != 0) {
2018-07-11 14:13:24 +00:00
log_perror("setresuid");
2018-07-03 17:26:06 +00:00
exit(1);
}
}
ssize_t writeall(int fd, const char *buf, size_t amount) {
size_t written = 0;
while(written < amount) {
ssize_t r = write(fd, buf + written, amount - written);
if(r < 0) {
return r;
}
written += r;
}
return written;
}
2018-07-16 18:52:44 +00:00
ssize_t writeall_str(int fd, const char *s) {
return writeall(fd, s, strlen(s));
}
bool handle_finger_response(int sock, const char *finger_response_file) {
FILE *f = fopen(finger_response_file, "r");
if(f == NULL) {
log_perror("fopen");
return false;
}
size_t line_size = 0;
char *line = NULL;
ssize_t line_len;
while((line_len = getline(&line, &line_size, f)) > 0) {
// Strip the newline
if(line[line_len - 1] == '\n') {
line[line_len - 1] = '\0';
line_len--;
}
// Send the line along with the correct newline
if(writeall(sock, line, line_len) < 0 || writeall_str(sock, "\r\n") < 0) {
log_perror("writeall / writeall_str");
free(line);
return false;
}
}
if(!feof(f)) {
log_perror("getline");
free(line);
2018-07-22 10:28:40 +00:00
return false;
}
free(line);
fclose(f);
2018-07-22 10:28:40 +00:00
return true;
}
void handle_userlist(int sock, bool force_multiline) {
2018-07-16 16:20:22 +00:00
// Go over users' home dirs and see if .finger-response is present
2018-07-22 10:28:40 +00:00
bool first_line = true;
2018-07-16 18:09:24 +00:00
for(;;) {
errno = 0;
2018-07-16 18:52:44 +00:00
struct passwd *passwd_entry = getpwent();
if(passwd_entry == NULL) {
2018-07-16 18:09:24 +00:00
if(errno == 0) {
2018-07-16 18:52:44 +00:00
// End of file
endpwent();
2018-07-16 18:09:24 +00:00
break;
} else {
2018-07-16 18:52:44 +00:00
log_perror("getpwent");
2018-07-16 18:09:24 +00:00
return;
}
2018-07-16 16:20:22 +00:00
}
2018-07-16 18:52:44 +00:00
char *finger_response_file;
if(asprintf(&finger_response_file, "%s/.finger-response", passwd_entry->pw_dir) < 0) {
log_perror("asprintf");
2018-07-22 10:28:40 +00:00
endpwent();
2018-07-16 18:52:44 +00:00
return;
}
2018-07-16 16:20:22 +00:00
2018-07-16 18:52:44 +00:00
if(access(finger_response_file, F_OK) == 0) {
2018-07-22 10:28:40 +00:00
if(force_multiline && !first_line) {
if(writeall_str(sock, "\r\n") < 0) {
log_perror("writeall_str (separating newline)");
endpwent();
return;
}
}
2018-07-16 18:52:44 +00:00
if(writeall_str(sock, passwd_entry->pw_name) < 0) {
2018-07-22 10:28:40 +00:00
log_perror("writeall_str (username)");
endpwent();
2018-07-16 18:52:44 +00:00
return;
}
2018-07-22 10:28:40 +00:00
const char *suffix = "\r\n";
if(force_multiline) {
suffix = ":\r\n";
}
if(writeall_str(sock, suffix) < 0) {
log_perror("writeall_str (suffix)");
endpwent();
2018-07-16 18:52:44 +00:00
return;
}
2018-07-22 10:28:40 +00:00
if(force_multiline) {
if(handle_finger_response(sock, finger_response_file) == false) {
endpwent();
return;
}
}
first_line = false;
2018-07-16 18:52:44 +00:00
}
2018-07-11 14:13:24 +00:00
}
}
void handle_query(int sock, const char *username) {
2018-07-16 18:52:44 +00:00
errno = 0;
struct passwd *passwd_entry = getpwnam(username);
if(passwd_entry == NULL) {
if(errno == 0) {
if(writeall_str(sock, "Can't finger\r\n") < 0) {
log_perror("writeall_str");
return;
}
} else {
log_perror("getpwent");
}
return;
}
char *finger_response_file;
if(asprintf(&finger_response_file, "%s/.finger-response", passwd_entry->pw_dir) < 0) {
log_perror("asprintf");
return;
}
if(access(finger_response_file, F_OK) != 0) {
if(writeall_str(sock, "Can't finger\r\n") < 0) {
2018-07-22 10:28:40 +00:00
log_perror("writeall_str (not found error)");
2018-07-16 18:52:44 +00:00
return;
}
} else {
2018-07-22 10:28:40 +00:00
handle_finger_response(sock, finger_response_file);
2018-07-11 14:13:24 +00:00
}
}
2018-07-03 17:26:06 +00:00
void handle_connection(int sock) {
size_t request_size = 0;
char *request = NULL;
for(;;) {
ssize_t amount_read;
char iobuf[1024];
amount_read = read(sock, &iobuf, sizeof(iobuf));
if(amount_read == 0) {
log_error("Client hung up\n");
shutdown(sock, SHUT_RDWR);
close(sock);
return;
} else if(amount_read < 0) {
2018-07-11 14:13:24 +00:00
log_perror("read");
2018-07-03 17:26:06 +00:00
shutdown(sock, SHUT_RDWR);
close(sock);
return;
}
size_t index = request_size;
request_size += amount_read;
if((request = realloc(request, request_size)) == NULL) {
2018-07-11 14:13:24 +00:00
log_perror("realloc");
2018-07-03 17:26:06 +00:00
exit(1);
}
memmove(request + index, iobuf, amount_read);
if(request_size >= 2 && request[request_size-2] == '\r' && request[request_size-1] == '\n') {
// Request read
break;
}
}
2018-07-22 10:28:40 +00:00
char *request_buffer = request;
2018-07-03 17:26:06 +00:00
request[request_size-2] = '\0';
2018-07-22 10:28:40 +00:00
// See if the multi-line format was requested
bool force_multiline = false;
if(request[0] == '/' && request[1] == 'W') {
if(request[2] == '\0') {
// Just "/W"
force_multiline = true;
request = &request[2];
} else if(request[2] == ' ') {
// "/W " as prefix
force_multiline = true;
request = &request[3];
}
}
2018-07-03 17:26:06 +00:00
if(!strcmp(request, "")) {
2018-07-22 10:28:40 +00:00
handle_userlist(sock, force_multiline);
2018-07-03 17:26:06 +00:00
} else {
2018-07-11 14:13:24 +00:00
handle_query(sock, request);
2018-07-03 17:26:06 +00:00
}
2018-07-22 10:28:40 +00:00
free(request_buffer);
2018-07-03 17:26:06 +00:00
shutdown(sock, SHUT_RDWR);
close(sock);
}
int main(void) {
setup_listen();
drop_privileges();
2018-07-03 17:26:06 +00:00
for(;;) {
int amount_ready = poll(listens, num_listens, -1);
if(amount_ready < 0) {
2018-07-11 14:13:24 +00:00
log_perror("poll");
2018-07-03 17:26:06 +00:00
exit(1);
}
for(size_t i = 0; i < num_listens && amount_ready > 0; i++) {
if(listens[i].revents & POLLIN) {
struct sockaddr_storage client_addr;
socklen_t addr_size = sizeof(client_addr);
int sock = accept(listens[i].fd, (struct sockaddr *)&client_addr, &addr_size);
handle_connection(sock);
amount_ready--;
}
}
}
}