isdn4k-utils/vbox3/vboxgetty/vboxgetty.c

735 lines
19 KiB
C
Raw Blame History

/*
** $Id: vboxgetty.c,v 1.4 1998/07/06 09:05:35 michael Exp $
**
** Copyright 1996-1998 Michael 'Ghandi' Herold <michael@abadonna.mayn.de>
**
** $Log: vboxgetty.c,v $
** Revision 1.4 1998/07/06 09:05:35 michael
** - New control file code added. The controls are not longer only empty
** files - they can contain additional informations.
** - Control "vboxctrl-answer" added.
** - Control "vboxctrl-suspend" added.
** - Locking mechanism added.
** - Configuration parsing added.
** - Some code cleanups.
**
** Revision 1.3 1998/06/18 12:38:18 michael
** - 2nd part of the automake/autoconf implementation (now compiles again).
**
** Revision 1.2 1998/06/17 17:01:24 michael
** - First part of the automake/autoconf implementation. Currently vbox will
** *not* compile!
**
*/
#include "../config.h"
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <limits.h>
#include <errno.h>
#include <fnmatch.h>
#include "log.h"
#include "tcl.h"
#include "modem.h"
#include "rc.h"
#include "vboxrc.h"
#include "userrc.h"
#include "voice.h"
#include "stringutils.h"
#include "tclscript.h"
#include "vboxgetty.h"
#include "control.h"
#include "lock.h"
/** Variables ************************************************************/
static char *progbasename;
static char *isdnttyname;
char temppathname[PATH_MAX + 1];
/** Structures ***********************************************************/
static struct vboxrc rc_getty_c[] =
{
{ "init" , NULL },
{ "initnumber" , NULL },
{ "badinitsexit" , NULL },
{ "initpause" , NULL },
{ "commandtimeout", NULL },
{ "echotimeout" , NULL },
{ "ringtimeout" , NULL },
{ "alivetimeout" , NULL },
{ "spooldir" , NULL },
{ "toggledtrtime" , NULL },
{ NULL , NULL }
};
static struct option arguments[] =
{
{ "version" , no_argument , NULL, 'v' },
{ "help" , no_argument , NULL, 'h' },
{ "debug" , required_argument, NULL, 'x' },
{ "device" , required_argument, NULL, 'd' },
{ NULL , 0 , NULL, 0 }
};
struct vboxmodem vboxmodem;
/** Prototypes ***********************************************************/
static int vboxgettyrc_parse(unsigned char *);
static int process_incoming_call(void);
static int run_modem_init(void);
static void pid_create(char *);
static void pid_remove(char *);
static void show_usage(int, int);
/*************************************************************************/
/** The magic main... **/
/*************************************************************************/
void main(int argc, char **argv)
{
char *stop;
int opts;
char *debugstr;
int debuglvl;
int i;
int modemstate;
int modeminits;
progbasename = argv[0];
if ((stop = rindex(argv[0], '/'))) progbasename = ++stop;
/* Die Argumente des Programms einlesen und den Debuglevel */
/* setzen. */
debugstr = NULL;
isdnttyname = NULL;
while ((opts = getopt_long(argc, argv, "vhx:d:", arguments, (int *)0)) != EOF)
{
switch (opts)
{
case 'x':
debugstr = optarg;
break;
case 'd':
isdnttyname = optarg;
break;
case 'v':
show_usage(200, 0);
break;
case 'h':
default:
show_usage(200, 1);
break;
}
}
if (debugstr)
{
if (strcasecmp(debugstr, "FULL") != 0)
{
debuglvl = LOG_E;
for (i = 0; i < strlen(debugstr); i++)
{
switch (debugstr[i])
{
case 'W':
case 'w':
debuglvl |= LOG_W;
break;
case 'I':
debuglvl |= LOG_I;
break;
case 'A':
debuglvl |= LOG_A;
break;
case 'D':
debuglvl |= LOG_D;
break;
}
}
}
else debuglvl = LOG_X;
log_set_debuglevel(debuglvl);
}
umask(xstrtoo(VBOX_ROOT_UMASK, 0));
/* Pfadangaben vom Devicenamen abschneiden und <20>berpr<70>fen ob */
/* das Device vom Benutzer gelesen und beschrieben werden */
/* kann (eigentlich nicht n<>tig, da nur unter Rootrechten ge- */
/* startet werden kann. */
if (isdnttyname)
{
if ((stop = rindex(isdnttyname, '/'))) isdnttyname = ++stop;
printstring(temppathname, "/dev/%s", isdnttyname);
if (access(temppathname, F_OK|R_OK|W_OK) != 0)
{
fprintf(stderr, "\n%s: error: \"%s\" doesn't exist or is not accessable!\n\n", progbasename, temppathname);
quit_program(100);
}
}
else
{
fprintf(stderr, "\n%s: error: isdn tty name is required!\n", progbasename);
show_usage(100, 1);
}
/* Pr<50>fen ob das Programm unter Rootrechten gestartet wurde. Die */
/* Rechte werden sp<73>ter auf die des jeweiligen Benutzers ge<67>n- */
/* dert, zum Start sind aber Rootrechte n<>tig. */
if (getuid() != 0)
{
fprintf(stderr, "\n%s: error: need root privilegs to start!\n\n", progbasename);
quit_program(100);
}
/* Jetzt wird der Log ge<67>ffnet. Der Name des aktuellen Devices */
/* wird an das Ende angeh<65>ngt. */
printstring(temppathname, "%s/vboxgetty-%s.log", LOGDIR, isdnttyname);
log_open(temppathname);
/* Tcl-Interpreter starten. F<>r die momentanen Funktionen wird */
/* Version 8 oder h<>her ben<65>tigt. */
if (scr_create_interpreter() == -1)
{
log_line(LOG_E, "Can't create/initialize the tcl interpreter!\n");
quit_program(100);
}
log_line(LOG_I, "Running vbox version %s (with tcl version %s).\n", VERSION, scr_tcl_version());
/* Konfiguration des getty's abarbeiten. Zuerst wird die globale, */
/* dann die des jeweiligen tty's eingelesen. */
if (vboxgettyrc_parse(isdnttyname) == -1)
{
log_line(LOG_E, "Unable to read/parse configuration!\n");
quit_program(100);
}
/* Modem Device <20>ffnen und die interne Initialisierung */
/* ausf<73>hren (nicht der normale Modeminit). */
printstring(temppathname, "/dev/%s", isdnttyname);
log_line(LOG_D, "Opening modem device \"%s\" (38400, CTS/RTS)...\n", temppathname);
if (vboxmodem_open(&vboxmodem, temppathname) == -1)
{
log_line(LOG_E, "Can't open/setup modem device (%s).\n", vboxmodem_error());
quit_program(100);
}
/* Lock- und PID-Datei f<>r den getty und das entsprechende */
/* Device erzeugen. */
printstring(temppathname, "%s/LCK..%s", LOCKDIR, isdnttyname);
if (lock_create(temppathname) == -1) quit_program(100);
printstring(temppathname, "%s/vboxgetty-%s.pid", PIDDIR, isdnttyname);
pid_create(temppathname);
/* Signalh<6C>ndler installieren. Alle m<>glichen Signale werden */
/* auf quit_program() umgelenkt. */
signal(SIGINT , quit_program);
signal(SIGTERM, quit_program);
/* Hauptloop: Der Loop wird nur verlassen, wenn w<>hrend der */
/* Abarbeitung ein Fehler aufgetreten ist. Das Programm be- */
/* endet sich danach! */
modemstate = VBOXMODEM_STAT_INIT;
modeminits = 0;
while (modemstate != VBOXMODEM_STAT_EXIT)
{
switch (modemstate)
{
case VBOXMODEM_STAT_INIT:
if (run_modem_init() == -1)
{
if ((i = (int)xstrtol(rc_get_entry(rc_getty_c, "badinitsexit"), 10)) > 0)
{
modeminits++;
if (modeminits >= i)
{
modemstate = VBOXMODEM_STAT_EXIT;
modeminits = 0;
log_line(LOG_E, "Exit program while bad init limit are reached.\n");
}
else log_line(LOG_W, "Bad initialization - Program will exist on %d trys!\n", (i - modeminits));
}
}
else
{
modemstate = VBOXMODEM_STAT_WAIT;
modeminits = 0;
}
break;
case VBOXMODEM_STAT_WAIT:
modem_flush(&vboxmodem, 0);
if (modem_wait(&vboxmodem) == 0)
{
modemstate = VBOXMODEM_STAT_RING;
modeminits = 0;
}
else modemstate = VBOXMODEM_STAT_TEST;
break;
case VBOXMODEM_STAT_TEST:
log_line(LOG_D, "Checking if modem is still alive...\n");
if (modem_command(&vboxmodem, "AT", "OK") > 0)
{
modemstate = VBOXMODEM_STAT_WAIT;
modeminits = 0;
}
else modemstate = VBOXMODEM_STAT_INIT;
break;
case VBOXMODEM_STAT_RING:
modem_set_nocarrier(&vboxmodem, 0);
process_incoming_call();
modem_hangup(&vboxmodem);
if (set_process_permissions(0, 0, xstrtoo(VBOX_ROOT_UMASK, 0)) != 0)
modemstate = VBOXMODEM_STAT_EXIT;
else
modemstate = VBOXMODEM_STAT_INIT;
break;
default:
log_line(LOG_E, "Unknown modem status %d!\n", modemstate);
modemstate = VBOXMODEM_STAT_INIT;
break;
}
}
quit_program(0);
}
/*************************************************************************/
/** quit_program(): Gibt alle belegten Resourcen frei und beendet das **/
/** Programm. **/
/*************************************************************************/
/** => rc R<>ckgabewert des Programms (1-99 ist reserviert). **/
/*************************************************************************/
void quit_program(int rc)
{
modem_hangup(&vboxmodem);
log_line(LOG_D, "Closing modem device (%d)...\n", vboxmodem.fd);
if (vboxmodem_close(&vboxmodem) != 0)
{
log_line(LOG_E, "%s (%s)\n", vboxmodem_error(), strerror(errno));
}
if (isdnttyname)
{
printstring(temppathname, "%s/LCK..%s", LOCKDIR, isdnttyname);
lock_remove(temppathname);
printstring(temppathname, "%s/vboxgetty-%s.pid", PIDDIR, isdnttyname);
pid_remove(temppathname);
}
scr_remove_interpreter();
rc_free(rc_getty_c);
log_close();
exit(rc);
}
/*************************************************************************/
/** show_usage(): Zeigt Benutzermeldung/Version an und beendet dann das **/
/** Programm. **/
/*************************************************************************/
/** => rc R<>ckgabewert des Programms (1-99 ist reserviert). **/
/** => help 1 wenn die Benutzermeldung oder 0 wenn die Version **/
/** angezeigt werden soll. **/
/*************************************************************************/
static void show_usage(int rc, int help)
{
if (help)
{
fprintf(stdout, "\n");
fprintf(stdout, "Usage: %s [OPTION] [OPTION] [...]\n", progbasename);
fprintf(stdout, "\n");
fprintf(stdout, "--device TTY Name of the isdn tty to use (required).\n");
fprintf(stdout, "--debug CODE Sets debug level (default \"EWI\").\n");
fprintf(stdout, "--version Display version and exit.\n");
fprintf(stdout, "--help Display this help and exit.\n");
fprintf(stdout, "\n");
}
else
{
fprintf(stdout, "%s version %s\n", progbasename, VERSION);
}
exit(rc);
}
/*************************************************************************/
/** run_modem_init(): Startet das Tcl-Skript zum initislisieren des **/
/** Modems. **/
/*************************************************************************/
/** <= 0 wenn die Initialisierung geklappt hat, -1 bei **/
/** einem Fehler. **/
/*************************************************************************/
static int run_modem_init(void)
{
struct vbox_tcl_variable vars[] =
{
{ "vbxv_init" , rc_get_entry(rc_getty_c, "init" ) },
{ "vbxv_initnumber" , rc_get_entry(rc_getty_c, "initnumber") },
{ NULL , NULL }
};
log_line(LOG_D, "Initializing modem...\n");
if (scr_init_variables(vars) == 0)
{
if (scr_execute("initmodem.tcl", NULL) == 0) return(0);
}
log_line(LOG_E, "Can't initialize modem device!\n");
return(-1);
}
/*************************************************************************/
/** vboxgettyrc_parse(): Liest die Konfiguration des gettys ein. Zu- **/
/** erst wird die globale und dann die des je- **/
/** weiligen tty's eingelesen. **/
/*************************************************************************/
/** => tty Name des benutzten tty's. **/
/** **/
/** <= 0 wenn alles eingelesen werden konnte oder **/
/** -1 bei einem Fehler. **/
/*************************************************************************/
static int vboxgettyrc_parse(unsigned char *tty)
{
printstring(temppathname, "%s/vboxgetty.conf", SYSCONFDIR);
if (rc_read(rc_getty_c, temppathname, NULL) < 0)
{
if (errno != ENOENT)
{
log_line(LOG_E, "Can't open \"%s\" (%s)!\n", temppathname, strerror(errno));
return(-1);
}
}
printstring(temppathname, "%s/vboxgetty.conf.%s", SYSCONFDIR, tty);
if (rc_read(rc_getty_c, temppathname, NULL) < 0)
{
if (errno != ENOENT)
{
log_line(LOG_E, "Can't open \"%s\" (%s)!\n", temppathname, strerror(errno));
return(-1);
}
}
log_line(LOG_D, "Filling unset configuration variables with defaults...\n");
if (!rc_set_empty(rc_getty_c, "init" , "ATZ&B512" )) return(-1);
if (!rc_set_empty(rc_getty_c, "badinitsexit" , "10" )) return(-1);
if (!rc_set_empty(rc_getty_c, "initpause" , "2500" )) return(-1);
if (!rc_set_empty(rc_getty_c, "commandtimeout" , "4" )) return(-1);
if (!rc_set_empty(rc_getty_c, "echotimeout" , "4" )) return(-1);
if (!rc_set_empty(rc_getty_c, "ringtimeout" , "6" )) return(-1);
if (!rc_set_empty(rc_getty_c, "alivetimeout" , "1800" )) return(-1);
if (!rc_set_empty(rc_getty_c, "toggledtrtime" , "400" )) return(-1);
if (!rc_set_empty(rc_getty_c, "spooldir" , "/var/spool/vbox")) return(-1);
modemsetup.echotimeout = xstrtol(rc_get_entry(rc_getty_c, "echotimeout" ), 4 );
modemsetup.commandtimeout = xstrtol(rc_get_entry(rc_getty_c, "commandtimeout"), 4 );
modemsetup.ringtimeout = xstrtol(rc_get_entry(rc_getty_c, "ringtimeout" ), 6 );
modemsetup.alivetimeout = xstrtol(rc_get_entry(rc_getty_c, "alivetimeout" ), 1800 );
modemsetup.toggle_dtr_time = xstrtol(rc_get_entry(rc_getty_c, "toggledtrtime" ), 400 );
if (!rc_get_entry(rc_getty_c, "initnumber"))
{
log_line(LOG_E, "Variable \"initnumber\" *must* be set!\n");
return(-1);
}
return(0);
}
/*************************************************************************/
/** process_incoming_call(): Bearbeitet einen eingehenden Anruf. **/
/*************************************************************************/
static int process_incoming_call(void)
{
struct vboxuser vboxuser;
struct vboxcall vboxcall;
char line[VBOXMODEM_BUFFER_SIZE + 1];
int haverings;
int waitrings;
int usersetup;
int ringsetup;
int inputisok;
char *stop;
memset(&vboxuser, 0, sizeof(vboxuser));
memset(&vboxcall, 0, sizeof(vboxcall));
haverings = 0;
waitrings = -1;
usersetup = 0;
ringsetup = 0;
while (modem_read(&vboxmodem, line, modemsetup.ringtimeout) == 0)
{
inputisok = 0;
/* Wenn der Benutzer der angerufenen Nummer ermittelt ist und */
/* dessen Konfigurations abgearbeitet wurde, wird <20>berpr<70>ft ob */
/* der Anruf angenommen werden soll. */
if ((usersetup) && (ringsetup))
{
if (waitrings >= 0)
{
if ((stop = ctrl_exists(vboxuser.home, "answer")))
{
log_line(LOG_D, "Control \"vboxctrl-answer:%s\" found...\n", stop);
if ((strcasecmp(stop, "no") == 0) || (strcasecmp(stop, "hangup") == 0) || (strcasecmp(stop, "reject") == 0))
{
log_line(LOG_D, "Incoming call will be rejected...\n");
return(0);
}
if (strcasecmp(stop, "now") != 0)
{
vboxuser.space = 0;
waitrings = xstrtol(stop, waitrings);
}
else
{
vboxuser.space = 0;
waitrings = 1;
}
log_line(LOG_D, "Call will be answered after %d ring(s).\n", waitrings);
}
}
if (waitrings > 0)
{
if (haverings >= waitrings)
{
return(voice_init(&vboxuser, &vboxcall));
}
}
}
/* Ring abarbeiten: Beim ersten Ring wird die angerufene */
/* Nummer gesichert, die durch ATS13.7=1 mit einem Slash */
/* an den Ringstring angeh<65>ngt ist. */
if (strncmp(line, "RING/", 5) == 0)
{
inputisok++;
haverings++;
if (!ringsetup)
{
xstrncpy(vboxuser.localphone, &line[5], VBOXUSER_NUMBER);
ringsetup = 1;
}
log_line(LOG_A, "%s #%03d (%s)...\n", line, haverings, (usersetup ? vboxcall.name : "not known"));
}
/* CallerID aus dem Modeminput kopieren. Wenn bereits die */
/* angerufene Nummer ermittelt wurde, wird einmalig die */
/* Konfigurationsdatei des Benutzers abgearbeitet. */
if (strncmp(line, "CALLER NUMBER: ", 15) == 0)
{
inputisok++;
if ((ringsetup) && (!usersetup))
{
xstrncpy(vboxuser.incomingid, &line[15], VBOXUSER_CALLID);
if (userrc_parse(&vboxuser, rc_get_entry(rc_getty_c, "spooldir"), vboxuser.localphone) == 0)
{
if ((vboxuser.uid != 0) && (vboxuser.gid != 0))
{
/* Nachdem "vboxgetty.user" abgearbeitet ist und */
/* ein Benutzer gefunden wurde, werden einige der */
/* Kontrolldateien gel<65>scht. */
ctrl_remove(vboxuser.home, "suspend");
if ((stop = ctrl_exists(vboxuser.home, "answer")))
{
if ((strcasecmp(stop, "no") == 0) || (strcasecmp(stop, "hangup") == 0) || (strcasecmp(stop, "reject") == 0))
{
ctrl_remove(vboxuser.home, "answer");
}
}
/* Die "effective Permissions" des Prozesses auf */
/* die des Benutzers setzen und dessen Konfigurat- */
/* ionsdatei abarbeiten. */
if (set_process_permissions(vboxuser.uid, vboxuser.gid, vboxuser.umask) == 0)
{
usersetup = 1;
waitrings = vboxrc_parse(&vboxcall, vboxuser.home, vboxuser.incomingid);
if (waitrings <= 0)
{
if (waitrings < 0)
log_line(LOG_W, "Incoming call will be ignored!\n");
else
log_line(LOG_D, "Incoming call will be ignored (user setup)!\n");
}
else log_line(LOG_D, "Call will be answered after %d ring(s).\n", waitrings);
}
}
else log_line(LOG_W, "Useing uid/gid 0 is not allowed - call will be ignored!\n", vboxuser.incomingid);
}
else log_line(LOG_W, "Number \"%s\" not bound to a local user - call will be ignored!\n", vboxuser.localphone);
}
}
if (!inputisok)
{
log_line(LOG_D, "Got junk line \"");
log_code(LOG_D, line);
log_text(LOG_D, "\"...\n");
continue;
}
}
return(-1);
}
/*************************************************************************/
/** set_process_permissions(): Setzt die effektive uid/gid des Pro- **/
/** zesses und die umask. **/
/*************************************************************************/
int set_process_permissions(uid_t uid, gid_t gid, int mask)
{
log_line(LOG_D, "Setting effective permissions to %d.%d [%04o]...\n", uid, gid, mask);
if (setegid(gid) == 0)
{
if (seteuid(uid) == 0)
{
if (mask != 0) umask(mask);
return(0);
}
else log_line(LOG_E, "Can't set effective uid to %d!\n", uid);
}
else log_line(LOG_E, "Can't set effective gid to %d!\n", gid);
return(-1);
}
/*************************************************************************/
/** pid_create(): Erzeugt die PID Datei f<>r den getty. **/
/*************************************************************************/
/** => name Name der Datei. **/
/*************************************************************************/
static void pid_create(char *name)
{
FILE *pptr;
log_line(LOG_D, "Creating \"%s\"...\n", name);
if ((pptr = fopen(name, "w")))
{
fprintf(pptr, "%ld\n", getpid());
fclose(pptr);
}
}
/*************************************************************************/
/** pid_remove(): L<>scht die PID Datei des getty. **/
/*************************************************************************/
/** => name Name der Datei. **/
/*************************************************************************/
static void pid_remove(char *name)
{
log_line(LOG_D, "Removing \"%s\"...\n", name);
remove(name);
}