From 17287576555a5c46fa23549e2e5f073660dccb70 Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Wed, 21 Apr 1999 09:51:03 +0200 Subject: Import ax25-tools 0.0.1 from tarball --- ax25/axspawn.c | 911 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 911 insertions(+) create mode 100644 ax25/axspawn.c (limited to 'ax25/axspawn.c') diff --git a/ax25/axspawn.c b/ax25/axspawn.c new file mode 100644 index 0000000..88381cf --- /dev/null +++ b/ax25/axspawn.c @@ -0,0 +1,911 @@ +/* + * + * $Id: axspawn.c,v 1.6 1996/08/24 22:33:05 jreuter Exp jreuter $ + * + * axspawn.c - run a program from ax25d. + * + * Copyright (c) 1996 Jörg Reuter DL1BKE (jreuter@poboxes.com) + * + * This program is a hack. + * + * 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. + * + * It might even kill your cat... ;-) + * + * Status: alpha (still...) + * + * usage: change the "default" lines in your /usr/local/etc/ax25d.conf: + * + * default * * * * * 1 root /usr/local/sbin/axspawn axspawn + * + * a line like this would wait for an incoming info frame first. + * + * default * * * * * 1 root /usr/local/sbin/axspawn axspawn --wait + * + * The program will check if the peer is an AX.25 socket, the + * callsign is a valid amateur radio callsign, strip the SSID, + * check if UID/GID are valid, allow a password-less login if the + * password-entry in /etc/passwd is "+" or empty; in every other case + * login will prompt for a password. + * + * Still on my TODO list: a TheNet compatible or MD5 based + * authentication scheme... Won't help you if you changed the "+"-entry + * in /etc/passwd to a valid passord and login with telnet, though. + * A better solution could be a small program called from .profile. + * + * Axspawn can create user accounts automatically. You may specify + * the user shell, first and maximum user id, group ID in the config + * file and (unlike WAMPES) create a file "/usr/local/etc/ax25.profile" + * which will be copied to ~/.profile. + * + * This is an example for the config file: + * + * # this is /usr/local/etc/axspawn.conf + * # + * # allow automatic creation of user accounts + * create yes + * # + * # guest user if above is 'no' or everything else fails. Disable with "no" + * guest ax25 + * # + * # group id or name for autoaccount + * group ax25 + * # + * # first user id to use + * first_uid 400 + * # + * # maximum user id + * max_uid 2000 + * # + * # where to add the home directory for the new user + * home /home/ax25 + * # + * # user's shell + * shell /bin/bash + * # + * # bind user id to callsign for outgoing connects. + * associate yes + * + * SECURITY: + * + * Umm... auto accounting is a security problem by definition. Unlike + * WAMPES, which creates an empty password field, Axspawn adds an + * "impossible" ('+') password to /etc/passwd. Login gets called with + * the "-f" option, thus new users have the chance to login without + * a password. (I guess this won't work with the shadow password system). + * + * The "associate" option has to be used with great care. If a user + * logs on it removes any existing callsign from the translation table + * for this userid and replaces it with the callsign and SSID of the + * user. This will happen with multiple connects (same callsign, + * different SSIDs), too. Unless you want your users to be able + * to call out from your machine disable "associate". + * + * Of course Axspawn does callsign checking: Only letters and numbers + * are allowed, the callsign must be longer than 4 characters and + * shorter than 6 characters (without SSID). There must be at least + * one digit, and max. two digits within the call. The SSID must + * be within the range of 0 and 15. Please drop me a note if you + * know a valid Amateur Radio (sic!) callsign that does not fit this + * pattern _and_ can be represented correctly in AX.25. + * + * It uses the forkpty from libbsd.a (found after analyzing logind) + * which has no prototype in any of my .h files. + * + */ + +/* removed -h from login command as it was causing hostname lookups + with new login/libc - Terry, vk2ktj. */ + + +#define QUEUE_DELAY 400 /* 400 msec */ +#define USERPROFILE ".profile" +#define PASSWDFILE "/etc/passwd" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "../pathnames.h" + +#define MAXLEN strlen("DB0PRA-15") +#define MINLEN strlen("KA9Q") + +#define AX_PACLEN 256 +#define NETROM_PACLEN 236 +#define ROSE_PACLEN 128 + +#define IS_DIGIT(x) ( (x >= '0') && (x <= '9') ) +#define IS_LETTER(x) ( (x >= 'A') && (x <= 'Z') ) + +#define MSG_NOCALL "Sorry, you are not allowed to connect.\n" +#define MSG_CANNOTFORK "Sorry, system is overloaded.\n" +#define MSG_NOPTY "Sorry, all channels in use.\n" +#define MSG_NOTINDBF "Sorry, you are not in my database\n" + +#define EXITDELAY 10 + +char policy_add_user = 1; +char policy_guest = 1; +char policy_associate = 0; + +gid_t user_gid = 400; +char *user_shell = "/bin/bash"; +char *start_home = "/home/funk"; +char *guest = "guest"; +int start_uid = 400; +int end_uid = 65535; +int paclen = ROSE_PACLEN; /* Its the shortest ie safest */ + +struct write_queue { + struct write_queue *next; + char *data; + int len; +}; + +struct write_queue *wqueue_head = NULL; +struct write_queue *wqueue_tail = NULL; +long wqueue_length = 0; + +/* This one is in /usr/lib/libbsd.a, but not in bsd.h and fellows... weird. */ +/* (found in logind.c) */ + +pid_t forkpty(int *, char *, void *, struct winsize *); + +int _write_ax25(const char *s, int len) +{ + int k, m; + char *p; + + p = (char *) malloc(len+1); + + if (p == NULL) + return 0; + + m = 0; + for (k = 0; k < len; k++) + { + if ( (s[k] == '\r') && ((k+1) < len) && (s[k+1] == '\n') ) + continue; + else if (s[k] == '\n') + p[m++] = '\r'; + else + p[m++] = s[k]; + } + + if (m) + write(1, p, m); + + free(p); + return len; +} + +int read_ax25(char *s, int size) +{ + int len = read(0, s, size); + int k; + + for (k = 0; k < len; k++) + if (s[k] == '\r') s[k] = '\n'; + + return len; +} + +/* + * We need to buffer the data from the pipe since bash does + * a fflush() on every output line. We don't want it, it's + * PACKET radio, isn't it? + */ + +void kick_wqueue(int dummy) +{ + char *s, *p; + struct write_queue *buf; + + + if (wqueue_length == 0) + return; + + s = (char *) malloc(wqueue_length); + + p = s; + + while (wqueue_head) + { + buf = wqueue_head; + wqueue_head = buf->next; + + memcpy(p, buf->data, buf->len); + p += buf->len; + free(buf->data); + free(buf); + } + + _write_ax25(s, wqueue_length); + free(s); + wqueue_tail=NULL; + wqueue_length=0; +} + +int write_ax25(const char *s, int len) +{ + struct itimerval itv, oitv; + struct write_queue * buf; + + signal(SIGALRM, SIG_IGN); + + buf = (struct write_queue *) malloc(sizeof(struct write_queue)); + if (buf == NULL) + return 0; + + buf->data = (char *) malloc(len); + if (buf->data == NULL) + return 0; + + memcpy(buf->data, s, len); + buf->len = len; + buf->next = NULL; + + if (wqueue_head == NULL) + { + wqueue_head = buf; + wqueue_tail = buf; + wqueue_length = len; + } else { + wqueue_tail->next = buf; + wqueue_tail = buf; + wqueue_length += len; + } + + if (wqueue_length >= paclen) + { + kick_wqueue(0); + } else { + itv.it_interval.tv_sec = 0; + itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = QUEUE_DELAY; + + setitimer(ITIMER_REAL, &itv, &oitv); + signal(SIGALRM, kick_wqueue); + } + return len; +} + +int get_assoc(struct sockaddr_ax25 *sax25) +{ + FILE *fp; + int uid; + char buf[81]; + + fp = fopen(PROC_AX25_CALLS_FILE, "r"); + if (!fp) return -1; + + fgets(buf, sizeof(buf)-1, fp); + + while(!feof(fp)) + { + if (fscanf(fp, "%d %s", &uid, buf) == 2) + if (sax25->sax25_uid == uid) + { + ax25_aton_entry(buf, (char *) &sax25->sax25_call); + return 0; + } + } + + return -1; +} + + +void cleanup(char *tty) +{ + struct utmp ut, *ut_line; + FILE *fp; + + setutent(); + ut.ut_type = LOGIN_PROCESS; + strncpy(ut.ut_id, tty + 3, sizeof(ut.ut_line)); + ut_line = getutid(&ut); + + if (ut_line != NULL) { + ut_line->ut_type = DEAD_PROCESS; + ut_line->ut_host[0] = '\0'; + ut_line->ut_user[0] = '\0'; + time(&ut_line->ut_time); + pututline(ut_line); + if ((fp = fopen(_PATH_WTMP, "r+")) != NULL) { + fseek(fp, 0L, SEEK_END); + if (fwrite(ut_line, sizeof(ut), 1, fp) != 1) + syslog(LOG_ERR, "Ooops, I think I've just barbecued your wtmp file\n"); + fclose(fp); + } + } + + endutent(); +} + + +/* + * add a new user to /etc/passwd and do some init + */ + +void new_user(char *newuser) +{ + struct passwd pw, *pwp; + uid_t uid; + FILE *fp; + char username[80]; + char homedir[256], userdir[256]; + char buf[4096]; + char subdir[4]; + int cnt; + unsigned char *p, *q; + struct stat fst; + int fd_a, fd_b, fd_l; + + /* + * build path for home directory + */ + + strncpy(subdir, newuser, 3); + subdir[3] = '\0'; + sprintf(username, "%s", newuser); + sprintf(homedir, "%s/%s.../%s", start_home, subdir, newuser); + strcpy(userdir, homedir); + + fd_l = open(LOCK_AXSPAWN_FILE, O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); + flock(fd_l, LOCK_EX); + +retry: + /* + * find first free UID + */ + + for (uid = start_uid; uid < 65535; uid++) + { + pwp = getpwuid(uid); + if (pwp == NULL) + break; + } + + if (uid >= 65535 || uid < start_uid) + return; + + /* + * build directories for home + */ + + p = homedir; + + while (*p == '/') p++; + + chdir("/"); + + while(p) + { + q = strchr(p, '/'); + if (q) + { + *q = '\0'; + q++; + while (*q == '/') q++; + if (*q == 0) q = NULL; + } + + if (stat(p, &fst) < 0) + { + if (errno == ENOENT) + { + mkdir(p, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + + if (q == NULL) + { + chown(p, uid, user_gid); + chmod(p, S_IRUSR|S_IWUSR|S_IXUSR); + } + } + else + return; + } + + if (chdir(p) < 0) + return; + p = q; + } + + /* + * add the user now + */ + + fp = fopen(PASSWDFILE, "a+"); + if (fp == NULL) + return; + + pw.pw_name = newuser; + pw.pw_passwd = "+"; + pw.pw_uid = uid; + pw.pw_gid = user_gid; + pw.pw_gecos = username; + pw.pw_dir = userdir; + pw.pw_shell = user_shell; + + if (getpwuid(uid) != NULL) goto retry; /* oops?! */ + + if (putpwent(&pw, fp) < 0) + return; + + flock(fd_l, LOCK_UN); + fclose(fp); + + /* + * copy ax25.profile + */ + + fd_a = open(CONF_AXSPAWN_PROF_FILE, O_RDONLY); + + if (fd_a > 0) + { + fd_b = open(USERPROFILE, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IXUSR); + + if (fd_b < 0) + return; + + while ( (cnt = read(fd_a, &buf, sizeof(buf))) > 0 ) + write(fd_b, &buf, cnt); + close(fd_b); + close(fd_a); + chown(USERPROFILE, uid, user_gid); + } +} + +void read_config(void) +{ + FILE *fp = fopen(CONF_AXSPAWN_FILE, "r"); + char buf[512]; + char cmd[40], param[80]; + char *p; + + if (fp == NULL) + return; + + while (!feof(fp)) + { + fgets(buf, sizeof(buf), fp); + p = strchr(buf, '#'); + if (p) *p='\0'; + + if (buf[0] != '\0') + { + sscanf(buf, "%s %s", cmd, param); + + if (!strncmp(cmd, "create", 5)) + { + policy_add_user = (param[0] == 'y'); + } else + if (!strncmp(cmd, "guest", 5)) + { + if (!strcmp(param, "no")) + { + policy_guest = 0; + } else { + policy_guest = 1; + guest = (char *) malloc(strlen(param)+1); + strcpy(guest, param); + } + } else + if (!strncmp(cmd, "group", 5)) + { + user_gid = strtol(param, &p, 0); + if (*p != '\0') + { + struct group * gp = getgrnam(param); + if (gp != NULL) + user_gid = gp->gr_gid; + else + user_gid = 400; + endgrent(); + } + } else + if (!strncmp(cmd, "first", 5)) + { + start_uid = strtol(param, &p, 0); + if (*p != '\0') + start_uid = 400; + } else + if (!strncmp(cmd, "max", 3)) + { + end_uid = strtol(param, &p, 0); + if (*p != '\0') + end_uid = 0; + } else + if (!strncmp(cmd, "home", 4)) + { + start_home = (char *) malloc(strlen(param)+1); + strcpy(start_home, param); + } else + if (!strncmp(cmd, "assoc", 5)) + { + if (!strcmp(param, "yes")) + policy_associate = 1; + else + policy_associate = 0; + } else + if (!strncmp(cmd, "shell", 5)) + { + user_shell = (char *) malloc(strlen(param)+1); + strcpy(user_shell, param); + } else + { + printf("error in config: ->%s %s<-\n", cmd, param); + } + } + } + + fclose(fp); +} + +char ptyslave[20]; +int child_pid; + +void signal_handler(int dummy) +{ + kill(child_pid, SIGHUP); + cleanup(ptyslave+5); + exit(1); +} + +int main(int argc, char **argv) +{ + char call[20], user[20], real_user[20]; + char buf[2048]; + int k, cnt, digits, letters, invalid, ssid, ssidcnt, addrlen; + int fdmaster; + pid_t pid = -1; + char *p; + fd_set fds_read, fds_err; + struct passwd *pw; + int chargc; + char *chargv[20]; + int envc; + char *envp[20]; + char wait_for_tcp; + struct utmp ut_line; + struct winsize win = { 0, 0, 0, 0}; + struct sockaddr_ax25 sax25; + union { + struct full_sockaddr_ax25 fsax25; + struct sockaddr_rose rose; + } sockaddr; + char *protocol; + + digits = letters = invalid = ssid = ssidcnt = 0; + + if (argc > 1 && (!strcmp(argv[1],"-w") || !strcmp(argv[1],"--wait"))) + wait_for_tcp = 1; + else + wait_for_tcp = 0; + + read_config(); + + openlog("axspawn", LOG_PID, LOG_DAEMON); + + if (getuid() != 0) { + printf("permission denied\n"); + syslog(LOG_NOTICE, "user %d tried to run axspawn\n", getuid()); + return 1; + } + + addrlen = sizeof(struct full_sockaddr_ax25); + k = getpeername(0, (struct sockaddr *) &sockaddr, &addrlen); + + if (k < 0) { + syslog(LOG_NOTICE, "getpeername: %m\n"); + return 1; + } + + switch (sockaddr.fsax25.fsa_ax25.sax25_family) { + case AF_AX25: + strcpy(call, ax25_ntoa(&sockaddr.fsax25.fsa_ax25.sax25_call)); + protocol = "AX.25"; + paclen = AX_PACLEN; + break; + + case AF_NETROM: + strcpy(call, ax25_ntoa(&sockaddr.fsax25.fsa_ax25.sax25_call)); + protocol = "NET/ROM"; + paclen = NETROM_PACLEN; + break; + + case AF_ROSE: + strcpy(call, ax25_ntoa(&sockaddr.rose.srose_call)); + protocol = "Rose"; + paclen = ROSE_PACLEN; + break; + + default: + syslog(LOG_NOTICE, "peer is not an AX.25, NET/ROM or Rose socket\n"); + return 1; + } + + for (k = 0; k < strlen(call); k++) + { + if (ssidcnt) + { + if (!IS_DIGIT(call[k])) + invalid++; + else + { + if (ssidcnt > 2) + invalid++; + else if (ssidcnt == 1) + ssid = (int) (call[k] - '0'); + else + { + ssid *= 10; + ssid += (int) (call[k] - '0'); + + if (ssid > 15) invalid++; + } + ssidcnt++; + } + } else + if (IS_DIGIT(call[k])) + { + digits++; + if (k > 3) invalid++; + } else + if (IS_LETTER(call[k])) + letters++; + else + if (call[k] == '-') + { + if (k < MINLEN) + invalid++; + else + ssidcnt++; + } + else + invalid++; + } + + if ( invalid || (k < MINLEN) || (digits > 2) || (digits < 1) ) + { + write_ax25(MSG_NOCALL, sizeof(MSG_NOCALL)); + syslog(LOG_NOTICE, "%s is not an Amateur Radio callsign\n", call); + sleep(EXITDELAY); + return 1; + } + + strcpy(user, call); + strlwr(user); + p = strchr(user, '-'); + if (p) *p = '\0'; + strcpy(real_user, user); + + if (wait_for_tcp) + read_ax25(buf, sizeof(buf)); /* incoming TCP/IP connection? */ + + pw = getpwnam(user); + + if (pw == NULL) + { + if (policy_add_user) + { + new_user(user); + pw = getpwnam(user); + } + + if (pw == NULL && policy_guest) + { + strcpy(real_user,guest); + pw = getpwnam(guest); + + if (! (pw && pw->pw_uid && pw->pw_gid) ) + { + write_ax25(MSG_NOTINDBF, sizeof(MSG_NOTINDBF)); + syslog(LOG_NOTICE, "%s (callsign: %s) not found in /etc/passwd\n", user, call); + sleep(EXITDELAY); + return 1; + } + } + } + + endpwent(); + + if (pw->pw_uid == 0 || pw->pw_gid == 0) + { + write_ax25(MSG_NOCALL, sizeof(MSG_NOCALL)); + syslog(LOG_NOTICE, "root login of %s (callsign: %s) denied\n", user, call); + sleep(EXITDELAY); + return 1; + } + + /* + * associate UID with callsign (or vice versa?) + */ + + if (policy_associate) + { + int fds = socket(AF_AX25, SOCK_SEQPACKET, 0); + if (fds != -1) + { + sax25.sax25_uid = pw->pw_uid; + if (get_assoc(&sax25) != -1) + ioctl(fds, SIOCAX25DELUID, &sax25); + switch (sockaddr.fsax25.fsa_ax25.sax25_family) { + case AF_AX25: + case AF_NETROM: + sax25.sax25_call = sockaddr.fsax25.fsa_ax25.sax25_call; + break; + case AF_ROSE: + sax25.sax25_call = sockaddr.rose.srose_call; + break; + } + ioctl(fds, SIOCAX25ADDUID, &sax25); + close(fds); + } + } + + fcntl(1, F_SETFL, O_NONBLOCK); + + pid = forkpty(&fdmaster, ptyslave, NULL, &win); + + if (pid == 0) + { + struct termios termios; + + memset((char *) &termios, 0, sizeof(termios)); + + ioctl(0, TIOCSCTTY, (char *) 0); + + termios.c_iflag = ICRNL | IXOFF; + termios.c_oflag = OPOST | ONLCR; + termios.c_cflag = CS8 | CREAD | CLOCAL; + termios.c_lflag = ISIG | ICANON; + termios.c_cc[VINTR] = 127; + termios.c_cc[VQUIT] = 28; + termios.c_cc[VERASE] = 8; + termios.c_cc[VKILL] = 24; + termios.c_cc[VEOF] = 4; + cfsetispeed(&termios, B19200); + cfsetospeed(&termios, B19200); + tcsetattr(0, TCSANOW, &termios); + + setutent(); + ut_line.ut_type = LOGIN_PROCESS; + ut_line.ut_pid = getpid(); + strncpy(ut_line.ut_line, ptyslave + 5, sizeof(ut_line.ut_line)); + strncpy(ut_line.ut_id, ptyslave + 8, sizeof(ut_line.ut_id)); + strncpy(ut_line.ut_user, "LOGIN", sizeof(ut_line.ut_user)); + strncpy(ut_line.ut_host, protocol, sizeof(ut_line.ut_host)); + time(&ut_line.ut_time); + ut_line.ut_addr = 0; + pututline(&ut_line); + endutent(); + + chargc = 0; + chargv[chargc++] = "/bin/login"; + chargv[chargc++] = "-p"; + if (!strcmp(pw->pw_passwd, "+")) + chargv[chargc++] = "-f"; + chargv[chargc++] = real_user; + chargv[chargc] = NULL; + + envc = 0; + envp[envc] = (char *) malloc(30); + sprintf(envp[envc++], "AXCALL=%s", call); + envp[envc] = (char *) malloc(30); + sprintf(envp[envc++], "CALL=%s", user); + envp[envc] = (char *) malloc(30); + sprintf(envp[envc++], "PROTOCOL=%s", protocol); + envp[envc] = NULL; + + execve(chargv[0], chargv, envp); + } + else if (pid > 0) + { + child_pid = 0; + signal(SIGHUP, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); + signal(SIGQUIT, signal_handler); + + while(1) + { + FD_ZERO(&fds_read); + FD_ZERO(&fds_err); + FD_SET(0, &fds_read); + FD_SET(0, &fds_err); + FD_SET(fdmaster, &fds_read); + FD_SET(fdmaster, &fds_err); + + k = select(fdmaster+1, &fds_read, NULL, &fds_err, NULL); + + if (k > 0) + { + if (FD_ISSET(0, &fds_err)) + { + kill(pid, SIGHUP); + cleanup(ptyslave+5); + return 1; + } + + if (FD_ISSET(fdmaster, &fds_err)) + { + cleanup(ptyslave+5); + return 1; + } + + if (FD_ISSET(0, &fds_read)) + { + cnt = read_ax25(buf, sizeof(buf)); + if (cnt < 0) /* Connection died */ + { + kill(pid, SIGHUP); + cleanup(ptyslave+5); + return 1; + } else + write(fdmaster, buf, cnt); + } + + if (FD_ISSET(fdmaster, &fds_read)) + { + cnt = read(fdmaster, buf, sizeof(buf)); + if (cnt < 0) + { + cleanup(ptyslave+5); + return 1; /* Child died */ + } + write_ax25(buf, cnt); + } + } else + if (k < 0 && errno != EINTR) + { + kill(pid, SIGHUP); /* just in case... */ + cleanup(ptyslave+5); + return 0; + } + } + } + else + { + syslog(LOG_ERR, "cannot fork %m, closing connection to %s\n", call); + write_ax25(MSG_CANNOTFORK, sizeof(MSG_CANNOTFORK)); + sleep(EXITDELAY); + return 1; + } + + sleep(EXITDELAY); + + return 0; +} -- cgit v1.2.3