/* * * Murat's Ident Daemon * * (C) 1998-2002 Murat Deligonul * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * ------------------------ * FIXME: * * Checks for idle time limits and such are not * very efficient (at all?) right now. * * * num_conns and num_unix ought to be handled by the * linkedlist code. That is, the linkedlist should know * how many elements it has when we ask for it. * * * is_valid_num works but is borken * * * Fake ident lifetime seems to not work reliably? * They get deleted, but sometimes after the life * period. * * * I need to eliminate defs.h and make it all command * line configurable. * * * On FreeBSD socket name is truncated?? * */ #include "autoconf.h" #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "identd.h" #include "defs.h" #include "lib_mdidentd.h" static int daemon(bool cr); static int unix_listen_fd, listen_fd; static bool foreground, auto_kill; /* Some options */ bool no_real; static uid_t uid; /* Become this user after binding sock */ static unsigned int hard_limit; /* Max # of fake idents */ static unsigned int num_conns; static unsigned int num_unix; static char * identd_path; /* where secondary identd resides */ static char ** identd_args; static int num_cmdargs; static int max_time; /* How long fake ident requests will live */ static list conns; /* store the connections on port 113 */ static list unix_conns; /* store connections to the domain socket */ static ilist * table; /* store all of the fake idents */ int gettok(const char *, char *, unsigned long, char, int, bool = 0); int safe_strcpy(char *, const char *, unsigned long ); int fdprintf(int fd, const char *, ...); const char *MDIDENTD_VERSION = "0.85.5"; inline void usage(void) { fprintf(stderr, "mdidentd v%s - Murat Deligonul (druglord@freelsd.org) - (C) 1998-2002\n" "\nUsage:\n" "mdidentd [-f|-k|-h|-u] [options]\nWhere:\n" "-u : Setuid to this user after binding sockets\n" "-h : Hard fake ident limit (max # of fake idents to store at once\n" "-f: Stay in foreground\n" "-k: Immediately kill fake ident requests after they have been used\n" "-r: Prevent users from using setting idents that are real users on this machine\n" "\n" "identd: The path to your existing ident daemon.\n" " It will be launched by mdidentd if needed\n" " This is a required argument. Try /usr/sbin/in.identd if unsure.\n" "options Optional arguments to pass on to the ident daemon mentioned above\n", MDIDENTD_VERSION); } /* * FIXME: '6667ax a' is valid according to this * but "ax66667" is not */ static inline bool is_valid_num(const char * num) { bool success = 0; do { if (!isdigit(*num) && !isspace(*num) && !(*num == '\r' || *num == '\n')) return 0; success = 1; } while (*++num); return 1; } /* * Parse arguments including: * the identd to spawn, and what arguments to give to it * userid to become after getting port * path of domain socket to create * how long to keep custom idents alive. * * We don't make copies of the stuff in argv but that should be ok * for now. */ static bool parse_cmdline(int argc, char ** argv) { int cmdarg_start = 0; listen_fd = unix_listen_fd = -1; num_cmdargs = 0; foreground = auto_kill = 0; uid = 0; hard_limit = 200; identd_args = 0; num_unix = num_conns = 0; if (argc < 2) { usage(); return 0; } for (int y = 1; y < argc; y++) { switch (argv[y][0]) { case '-': switch (argv[y][1]) { case 'r': no_real = 1; break; case 'f': foreground = 1; break; case 'k': auto_kill = 1; break; case 'u': char * id; id = argv[y+1]; if (!id) { fprintf(stderr, "Option '-u' requires numeric user id to follow it\n"); exit(1); } uid = (uid_t) atoi(id); y++; break; case 'h': id = argv[y+1]; if ((!id) || !(hard_limit = (unsigned) atoi(id))) { fprintf(stderr, "Option '-h' requires a number greater than 0\n"); exit(1); } y++; break; default: fprintf(stderr, "Unknown option %c, but continuing anyway\n", argv[y][1]); } break; default: /* * Assume this is the start of the path & cmdline * for the other identd. argv[y] is the executable * and the rest is the arguments. * We set the other program's argv[0] ourselves. */ identd_path = argv[y]; cmdarg_start = y + 1; num_cmdargs = argc - (cmdarg_start) + 1; identd_args = new char*[num_cmdargs + 1 /* need one argument for argv[0] * to be given to spawned identd */ + 1 /* and one for NULL */]; identd_args[0] = identd_path; /* set argv[0] here */ for (int q = 1; q - 1 < num_cmdargs; q++) identd_args[q] = argv[cmdarg_start + q - 1]; identd_args[num_cmdargs] = NULL; /* we're done */ return 1; } } return 1; } static int listen(unsigned short port) { struct sockaddr_in sin; int parm = 1; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(port); listen_fd = socket(AF_INET, SOCK_STREAM, 0); setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char * ) &parm, sizeof(int)); if (listen_fd < 0 || bind(listen_fd, (struct sockaddr *) &sin, sizeof(sin)) < 0 || ::listen(listen_fd, 5) < 0) { close(listen_fd); listen_fd = -1; return 0; } return 1; } /* * Note: this effectively destroys whatever file * is at 'path'. * * Return: 1 success * 0 failure - check errno */ static int listen_unix(char const * path) { struct sockaddr_un thing; size_t len; if ((unix_listen_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) return 0; unlink(path); memset(&thing, 0, sizeof(thing)); thing.sun_family = AF_UNIX; safe_strcpy(thing.sun_path, path, sizeof(thing.sun_path)); len = sizeof(thing.sun_family) + strlen(thing.sun_path); if (bind(unix_listen_fd, (struct sockaddr *) &thing, len) < 0 || ::listen(unix_listen_fd, 3) < 0) { unix_listen_fd = -1; return 0; } /* Must make it world writable if we expect to receive connections on it */ fchmod(unix_listen_fd, 0777); return 1; } static int service_unix(conn * c) { table->read_from(c->fd); close_conn(c); return -1; } /* * Accept domain socket connection. * Does limits and such. */ static int accept_unix_connection(int fd) { struct sockaddr_un thing; socklen_t x = sizeof(thing); int fd2 = accept(fd, (struct sockaddr *) &thing, &x); if (fd2 < 0) return 0; if (table->get_num() >= hard_limit) { syslog(LOG_ERR, "Not accepting domain socket connection: hard limit on fake idents exceeded"); close(fd2); return 0; } conn * c = new conn; if (!c) { syslog(LOG_ERR, "accept: memory allocation error after accepting connection on domain sock"); close(fd2); return 0; } c->fd = fd2; c->bytes_in = 0; c->time = time(NULL); memset(c->input_buff, 0, sizeof(c->input_buff)); unix_conns.add(c); num_unix++; return 1; } static int accept_connection(int fd) { struct sockaddr_in sin; socklen_t len = sizeof(sin); int fd2 = accept(fd, (struct sockaddr *) &sin, &len); if (fd2 < 0) { syslog(LOG_ERR, "accept: %s", strerror(errno)); return 0; } conn * c = new conn; if (!c) { syslog(LOG_ERR, "accept: memory allocation error after accepting"); close(fd2); return 0; } c->fd = fd2; c->bytes_in = 0; c->time = time(NULL); memset(c->input_buff, 0, sizeof(c->input_buff)); conns.add(c); num_conns++; return 1; } /* * We need to read with MSG_PEEK, because the data must also be readable * to the other ident daemon if it needs to be spawned. * * Problems: * Socket will still be marked as readable afterwards. Select() will indicate * so. Will cause infinite loop. For that reason if there is no valid * ident response that we can reply to in the first recv attempt, * then the request will be given to the other identd. Better fix for this * issue? I don't know! * * Return -1 if 'c' should be destroyed. */ static int service(conn * c) { c->bytes_in = recv(c->fd, c->input_buff, sizeof(c->input_buff) - 1, MSG_PEEK); if (c->bytes_in <= 0) { if (errno) syslog(LOG_INFO, "Connection closed: %s", strerror(errno)); close_conn(c); return -1; } /* We must add null char to the end */ c->input_buff[c->bytes_in] = 0; reply(c); return -1; } static void close_conn(conn * c) { close(c->fd); c->fd = -1; c->bytes_in = 0; } /* * Read the ident request from its input buffer and * see if matches any of our fake ones, if not.. * its time to load the user's other identd. * * Currently replies system name as 'OTHER'. */ static int reply(conn * c) { char port1[6], port2[16]; unsigned short p1 = 0, p2 = 0; /* * We must have \n or \r or we drop it */ if (strchr(c->input_buff, '\r') || strchr(c->input_buff, '\n')) { if ((gettok(c->input_buff, port1, sizeof port1, ',', 1)) && (gettok(c->input_buff, port2, sizeof port2, ',', 2)) && (p1 = atoi(port1)) && (p2 = atoi(port2)) && is_valid_num(port1) && is_valid_num(port2)) { char ident[50]; struct sockaddr_in sin; socklen_t len = sizeof(sin); getpeername(c->fd, (struct sockaddr *) &sin, &len); if (table->lookup(p1, p2, ident, sizeof(ident))) { syslog(LOG_INFO, "Request from %s for: %d , %d ", inet_ntoa(sin.sin_addr), p1, p2); syslog(LOG_INFO, "(fake) reply: %d, %d : USERID : OTHER :%s", p1, p2, ident); fdprintf(c->fd, "%d , %d : USERID : OTHER :%s\r\n", p1, p2, ident); if (auto_kill) table->remove(p1, p2, ident); close_conn(c); return 1; } } } /* Have sex */ switch (fork()) { case 0: /* * This is the child: close out unneeded fds, * redirect stdin/err/out to socket. */ close(unix_listen_fd); close(listen_fd); dup2(c->fd, 0); /* close_conn(c); */ delete c; dup2(0, 1); dup2(0,2); signal(SIGPIPE, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGALRM, SIG_DFL); if (execv(identd_path, identd_args) < 0) syslog(LOG_ERR, "Unable to spawn %s: %s", identd_path, strerror(errno)); /* unreached */ exit(0); return 1; case -1: syslog(LOG_ERR, "Can't spawn %s: fork: %s", identd_path, strerror(errno)); default: close_conn(c); break; } return 1; } /* * Start the server and enter the main loop. * Create fd_sets, select, poll ilist. * Blah blah. */ static int start_server(time_t max) { fd_set fds; struct timeval tv; time_t select_time = time(NULL); time_t curtime; conn * c; max_time = max; table = new ilist(max_time); if (!table) return fprintf(stderr,"Memory allocation failure. Bailing out.\n"), 1; if (!listen(113)) return perror("While binding to port 113"), 1; if (!listen_unix(MDIDENTD_SOCK_PATH)) return perror(MDIDENTD_SOCK_PATH), 1; /* we already did this. but it doesn't work. i dunno why. * i am messing up somwhere. dunno where. but we will do it again. * and it will work. so there!! */ chmod(MDIDENTD_SOCK_PATH, 0777); #ifndef NOFORK if (!foreground) { switch (daemon(1)) { case -1: perror("While trying to go into the background"); return 0; case 0: break; default: return 0; } } #endif if (setuid(uid) != 0) syslog(LOG_ERR, "setuid(%d) failed: %s (but continuing anyway)", uid, strerror(errno)); /* Set up signals */ struct sigaction sa; sa.sa_flags = 0; sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); sa.sa_handler = &sighandler; sigaction(SIGCHLD, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGHUP, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); /* syslog setup */ openlog("mdidentd", LOG_PID, LOG_DAEMON); syslog(LOG_INFO, "Version %s starting. Using %s as secondary ident daemon.", MDIDENTD_VERSION, identd_path); list_iterator i(&conns); list_iterator i2(&unix_conns); while (69) { int hf = listen_fd > unix_listen_fd ? listen_fd : unix_listen_fd; i.set(0); i2.set(0); FD_ZERO(&fds); FD_SET(listen_fd, &fds); FD_SET(unix_listen_fd, &fds); while (i.has_next()) { c = i.next(); FD_SET(c->fd, &fds); hf = (hf > c->fd) ? hf : c->fd; } while (i2.has_next()) { c = i2.next(); FD_SET(c->fd, &fds); hf = (hf > c->fd) ? hf : c->fd; } tv.tv_sec = max_time; tv.tv_usec = 0; /* * Update the select time counter only if the new time would * be more than 'max_time' newer than the old one. */ if ((select_time - time(&curtime)) < -max_time) select_time = curtime; switch (select(hf + 1, &fds, (fd_set *) NULL, (fd_set *) NULL, max_time ? &tv : (struct timeval *) NULL)) { case 0: /* This is starting to look like the linux kernel... */ goto check_time; case -1: if (errno != EINTR) { syslog(LOG_ERR, "select: %s -- bailing out", strerror(errno)); return 0; } continue; } if (FD_ISSET(unix_listen_fd, &fds)) accept_unix_connection(unix_listen_fd); if (FD_ISSET(listen_fd,&fds)) accept_connection(listen_fd); /* * FIXME: these ought to be combined in a singe loop */ i.set(0); i2.set(0); time(&curtime); while (i.has_next()) { c = i.next(); if (c->fd != listen_fd && FD_ISSET(c->fd, &fds) && service(c) == -1) { delete c; i.remove(); num_conns--; } /* Check for idlers while we're here */ else if (c->fd != listen_fd && curtime - c->time >= MAX_IDENT_WAIT_TIME) { struct sockaddr_in sin; socklen_t size = sizeof(sin); getpeername(c->fd, (struct sockaddr *) &sin, &size); syslog(LOG_INFO, "Killing idling connection from %s", inet_ntoa(sin.sin_addr)); close_conn(c); delete c; i.remove(); num_conns--; } } while (i2.has_next()) { c = i2.next(); if (c->fd != unix_listen_fd && FD_ISSET(c->fd, &fds) && service_unix(c) == -1) { delete c; i2.remove(); num_unix--; } /* Check for idlers while we're here */ else if (c->fd != unix_listen_fd && curtime - c->time >= MAX_UNIX_WAIT_TIME) { close_conn(c); syslog(LOG_INFO, "Killing idling connection on domain socket"); delete c; i2.remove(); num_unix--; } } check_time: /* * Check for expired fake request and other things */ if ((curtime - select_time) >= max_time) table->kill_expired(curtime); } return 1; } int main(int argc, char **argv) { if (parse_cmdline(argc, argv)) return start_server(FAKE_IDENT_LIFETIME); return 1; } void sighandler(int sig) { switch (sig) { case SIGCHLD: /* Collect exit status from exiting child */ wait(&sig); break; case SIGTERM: case SIGINT: syslog(LOG_INFO, "Exiting on signal %d", sig); closelog(); delete[] identd_args; identd_args = 0; exit(0); case SIGHUP: syslog(LOG_INFO, "HANGUP signal, don't know what to do."); break; case SIGUSR1: syslog(LOG_INFO, "Dumping stats"); syslog(LOG_INFO, "Active unix conns: %d Inet conns: %d", num_unix, num_conns); syslog(LOG_INFO, "Fake ident requests in memory: %lu; lifetime is %d", table->get_num(), FAKE_IDENT_LIFETIME); syslog(LOG_INFO, " hard limit is %d", hard_limit); syslog(LOG_INFO, "Number served: billions and billions"); break; } } /* * Return pid of child to parent. * 0 to child. * -1 on error. */ static int daemon(bool cr) { pid_t xx; switch ((xx = fork())) { case 0: /* Child */ setsid(); close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); if (cr) chroot("/"); return 0; case -1: return -1; default: return xx; } } /* * Gets which'th token, seperated by sep * return 1 on success 0 on failure. * Last argument indicates whether. * * Null argument can be passed as buffer to indicate you only * want to check the existance of a token. */ int gettok(const char *p, char *buffer, unsigned long maxsize, char sep, int which, bool get_rest) { int numfound = 1; /* remove leading seps */ while (*p == sep) p++; do { if (numfound == which && *p) { char * next = strchr(p, sep); if ((get_rest && buffer) || (!next /* && *(p+1) */) ) { if (buffer) safe_strcpy(buffer, p, maxsize); return 1; } else if (next) { if (buffer) { u_long bytes2copy = (next - p) + 1; /* how many bytes must be copied, including NULL character */ if (bytes2copy >= maxsize) bytes2copy = maxsize; strncpy(buffer, p, bytes2copy); buffer[bytes2copy - 1] = 0; } return 1; } return 0; } /* check if this letter == sep, and the next letter is not sep */ if (*p == sep && *(p+1) != sep) numfound++; } while (*++p); /* failed */ return 0; } /* * Copies src to dest, pretends dest is maxsize long. * will truncate by 1 if needs room to add \0 (bad?) */ int safe_strcpy(char *dest, const char *src, unsigned long maxsize) { unsigned long len = strlen(src); if (len >=maxsize) len = maxsize - 1; memcpy(dest, src, len); dest[len] = 0; return 1; } int fdprintf(int fd, const char *format, ...) { static char __mbuffer[1024]; __mbuffer[0] = 0; va_list ap; va_start(ap, format); int len = vsnprintf(__mbuffer, sizeof(__mbuffer), format, ap); va_end(ap); return (write(fd, __mbuffer, len)); }