Impressum Kontakt

Dialin Server - Oder wie man mit einem Telefon einen Rechner fernsteuert

(balle)

Trotz DSL und ISDN Flatrate gibt es immer noch genügend Modembenutzer in Deutschland. Die meisten werden ein Voice- und Faxmodem ihr Eigen nennen, aber diese durchaus netten Eigenschaften werden in den seltesten Fällen genutzt. Dabei kann man vor allem mit den Voice Eigenschaften seines Modems sehr schön herum spielen und brauchbaren Zusatznutzen gewinnen wie z.B einen selbstprogrammierten Anrufbeantworter, der je nach Caller ID den Anrufer begrüsst, weiterleitet oder sonstige Aktionen (Programme) ausführt.

Um einen Dialin Server auf zu setzen, verwendet man am besten das Programm mgetty von Gert Döring. Ich empfehle Dir allerdings, dass Du Dir die neuesten Sourcen ( kein RPM odere andere Binary Klamotten) aus dem Netz saugst und sie von Hand compilierst. Die Sourcen gibt es unter: http://alpha.greenie.net/mgetty/.

Wenn Du nämlich automatisch den PPP Daemon starten möchtest, dann musst Du das Makefile anpassen. Um genauer zu sein die Compiler Flags. Öffne also nach dem entpacken das Makefile in einem Editor und suche die Zeile:

CFLAGS=Compiler-Flags

An diese Zeile hängst Du noch den Befehl -DAUTOPPP. Das wird aber auch alles im Makefile sehr gut beschrieben.

Nun musst Du noch die Datei policy.h-dist nach policy.h moven. Danach kannst Du wie gewohnt die Sourcen compilieren und installieren:

make ; make install.

Wenn Du nicht nur einen gewöhnlichen Dialin Server haben möchtest und ein Voice Modem besitzt, dann musst Du noch das Programm vgetty compilieren. Das befindet sich im Unterverzeichniss voice.

Nach der Installation kopierst man die Vgetty Configdatei voice.conf-dist als voice.conf ins etc Verzeichnis von mgetty (z.B. /usr/local/etc/mgetty+sendfax). Vielleicht sollte ich auch noch erwähnen, daß megtty durch sendfax die Funktionen bietet, daß Dein Fax Modem Faxe empfängt bzw. sendet. Doch darauf werde ich hier nicht eingehen...

Erstmal eine Übersicht über die Configdateien (die Configdateien zum Faxen werden wie gesagt nicht berücksichtigt). Die Configuration wird zunächst für einen gewöhnlichen Dialin Server (per PPP) erklärt:

dialin.config -- Diese Datei kann man mit hosts.allow vergleichen. Sie bestimmt von welchen Telefonnummern (Caller IDs) man diesen Dialin Server benutzen darf. Vorausgesetzt das Du nicht so arm dran bist wie ich und eine digitale Leitung Dein "Eigen" nennst bzw. Deine analoge Leitung die Caller ID mit bekommt.

login.config -- Hier wird fest gelegt, welches Programm als Login Programm für Deinen Dialin Server dienen soll. Standardmäßig ist dies /bin/login. Falls Du allerdings automatisch den PPP Daemon starten möchtest, damit sich Deine Kumpels per Modem und PPP Protocol bei Dir einwählen können, dann fügst Du die Zeile

/AutoPPP/ - ppp /usr/sbin/pppd auth -chap +pap login kdebug 7 debug

ein und kommentierst die Zeile über /bin/login aus.

Welche Optionen der PPP Daemon erwartet, hängt ganz von Deinen Erwartungen und auch von Deinem verwendeten Modem ab (und welche Protokolle Du zur Einwahl anbieten möchtest CHAP (verschlüsselt) oder Standard PAP (plain)). Die Default Einstellung funktioniert aber für die meisten Modemtypen und Wünsche. Für mehr Informationen: man pppd.

mgetty.config -- In dieser Datei sagst Du mgetty an welchem Port es Dein Modem findet, welche Geschwindigkeit (in Baud) verwendet werden soll, mit welchen Rechten (unter welcher UID) Dein Modem Device läuft und wenn Du ein nicht ganz gebräuchliches Modem verwendest, findest Du hier auch noch standardmäßig ein paar Einstellungen, die speziell auf diesen Modemtyp zu geschnitten sind. Außerdem kannst Du auch noch über die Funktion init-chat bzw. answer-chat genau bestimmen mit welchen Modem Commands Dein Modem initialisiert werden soll, was es als Antwort erwarten soll und wie es zu reagieren hat. Diese Option braucht man aber nur in äußerst exotischen Fällen oder wenn man sich quer durch die Innereien seines Modems hacken möchte. =)

Warum ich so bescheuert bin und diesem Thema ein eigenes Kapitel widme? Weil ich selbst zwei Mal damit auf die Schnauze gefallen bin und mir im Usenet nen Abriss holen durfte! ;-D

Faustregel: Starte mgetty niemals von der Konsole aus (auch wenn Du UID 0 Rechte hast!), weil mgetty dann nicht die Rechte besitzt um Dein Modem Device uneingeschränkt zu nutzen.

Du musst deshalb einen Eintrag in /etc/inittab erstellen und sicher stellen, daß mgetty in allen run leveln verfügbar ist. Deshalb ergänze /etc/inittab durch folgende Zeile:

S0::respawn:/usr/local/sbin/mgetty -x 9 ttyS0

Die ID (S0) von inittab muss dem Device entsprechen (ttyS0). Durch die Option respawn wird der mgetty Prozess nach dem schliessen immer wieder neu gestartet. Ansonsten ist er nämlich futsch, sobald der erste Anrufer aufgelegt hat!

Und hier auch noch ein paar nützliche Optionen beim starten von mgetty:

  • -x integer -- Setz den Debug Level (Die Logdatei findet man unter /tmp/mgetty.device)
  • -D -- Teilt mgetty mit, daß es das Modem nur im Data Modus ansprechen soll
  • -n (n as integer) -- Mgetty hebt nach dem n. klingeln ab.

Für mehr Optionen: man mgetty.

Nach dem Eintrag einfach noch den init Prozess neu starten: init -q. OK. Damit sollte Dein Dialin Server für PPP funktionieren.

Ich habe mich allerdings an dieses Problem gewagt, weil ich gerne mein Handy als Fernbedienung für meinen Rechner nutzen wollte. Tja, leider versteht mein Handy kein PPP... Was nun?

Doch nicht so voreilig! Erstmal will ich erklären wie man mit einem Voice Modem einen Anrufbeantworter simpalawanert oder wohl eher erstmal wie man vgetty configuriert.

Die zentrale Configdatei von vgetty ist die besagte voice.conf Datei. Hier legt man ebenfalls fest an welchem Port das Modem angeschlosssen ist: port ttyS0, in welchem Verzeichnis die Voice Dateien (aufgenommene sowie Dateien zum abspielen) abgelegt sind: voice_dir: /var/spool/voice.

Dann configuriert man wieder die Rechte des Modem Devices wie in mgetty.config (mit dem gleichen Commands), sowie den Port Speed. Anschließend kann man noch angeben wie die aufgenommenen Sound Files comprimiert werden sollen:

rec_compression integer

Mit welcher Geschwindigkeit die Aufnahme geschehen soll:

  • rec_speed integer -- 0 steht übrigens für so viel wie auto
  • rec_remove_silence boolean -- Ob die Stille nach der Aufnahme entfernt werden soll
  • rec_max_len und rec_min_len -- Wie lange die Aufnahme maximal bzw. minimal in Sekunden dauern soll
  • beep_frequency und beep_length -- Wie sich ein Pfeif Ton vom Modem anhören soll (vergleichbar mit den Einstellungen von setterm)

Und schließlich als Creme de la Creme:

voice_shell interpreter-- Mit welchen Interpreter ein Programm aufgerufen werden soll, daß auf verschiedene Modem Ereignisse wie z.B. Voice Detect reagieren soll und...

call_programm programm -- ...natürlich welches Programm aufgerufen werden soll

Das starten von vgetty ist (wenn mans weiß) sehr einfach. Für vgetty gelten natürlich die gleichen Bedingungen wie für mgetty und deshalb tauscht man den Eintrag von mgetty in der /etc/inittab einfach gegen vgetty aus. Netter Weise sind sogar die Optionen die gleichen.

Gleich vorweg: Ein Beispiel Script wie man einen Anrufbeantworter programmiert, wird einem gleich mal in Form eines Perl Scriptes mit geliefert. Schau Dir mal in den mgetty Sourcen unter voice/Perl/ das Script answering_machine.pl an.

Vgetty installiert dafür ein Perl Modul Modem::Vgetty, das es natürlich auch im CPAN (http://www.cpan.org) gibt, mit dem man sein Voice Modem programmieren kann.

Nett! So langsam fängt die Sache an interessant zu werden! =) Hier doch noch mal ein kurzes Perl Script, das nach dem abheben, dem Anrufer eine Sound Datei vorspielt und danach das Gespräch aufzeichnet:

#!/usr/bin/perl
# Lade das Vgetty Modul
use Modem::Vgetty;
# Modem::Vgetty Objekt
my $modem = new Modem::Vgetty;
# Spiele eine RMD Sound Datei ab und warte bis das Modem wieder bereit ist
$modem->play_and_wait('/var/spool/voice/greets.rmd');
# Nehme danach das Gespräch auf
$modem->record('/var/spool/voice/incoming/test.rmd');

Ist doch easy, oder? =)

Die Sound Dateien, die das Modem erwartet bzw. aufnimmt, werden im RMD (Raw Modem Data) Format gespeichert, doch dazu gleich mehr.

Schön jetzt haben wir unser Voice Modem in einen Anrufbeantworter verwandelt, um weder Freundin noch Vermieter zu erschrecken...

...aber wie zum Teufel können wir das Ding jetzt dazu verwenden, um endlich den Rechner fern zu steuern???

Ist ja in Ordnung! Ich komm jetzt mal zum eigentlichen Thema! :-p

Das Modem::Vgetty Modul unterstützt neben den eben genannten Methoden auch noch Event Handler wie z.B. DIAL_TONE (das Modem wählt gerade), VOICE_DETECTED (das Modem hat erkannt, daß jemand mit ihm labert / funktioniert leider nicht bei jedem Modem Typ), SILENCE_DETECTED (da geht nix ab auf der Leitung) und was jetzt am meisten interessiert RECEIVE_DTMF (jemand sendet DTMF Töne über die Leitung, indem er z.B. Tasten auf seinem Handy drückt).

Durch diese Funktion wird einerseits ermöglicht mit vgetty ein Voice Menu (drücken Sie die 1, wenn Sie Ihre Kreditkartennummer übermitteln wollen) zu programmieren oder aber die Eingabe einer (mehrerer) PIN Nummer.

#!/usr/bin/perl  
use Modem::Vgetty;  
my $modem = new Modem::Vgetty;  
# Registriere ein Event beim Modem und aktiviere den Handler  
   $modem->add_handler('RECEIVE_DTMF','callback_id',\&callback);
$modem->enable_events;  
sub callback_routine  
{  
# Man bekommt eine Referent auf das Objekt, eventuell übergebene  
# Parameter und den eigentlichen DTMF Ton als Integer  
my($object, $parameter, $dtmf_tone) = @_;  
print STDERR "Der Anrufer hat eine die Taste  $dtmf_tone gedrückt!\n";  
}  

Mit diesem Wissen ausgestattet können wir schon ein Perl Programm schreiben, dass sich erst mal als ein ganz gewöhnlicher Anrufbeantworter ausgibt, aber auch auf Tasteneingaben reagiert und wir können (voraus gesetzt man hat keine analoge Leitung :-( ), auf die Caller ID reagieren. Das kann man selbst verständlich auch aus dem Perl Programm heraus, um z.B. seinen Vermieter oder seine Freundin ganz speziell zu begrüßen oder aber um die PIN Eingabe nur von bestimmten Telefonnummern zu zu lasssen (Security lässt grüssen! =) So nebenbei: Ich bin kein Telefon Phreak, gibt's eigentlich auch Spoofing für Telefonnummern??

Wer auf Nummer sicher gehen möchte, der kann auch Mgetty anweisen einen Callback durch zu führen. Dann gibt man z.B. eine richtige PIN ein, der Server ruft darauf hin bei einem auf dem Handy an (nicht die CALLER_ID!!) und übermittelt einem die zufällig errechnete PIN, um den Rechner online zu schalten. Dieses Verfahren hab ich hier allerdings nicht verwendet, sollte nur so als Überlegung dienen.

Auf jeden Fall wird die Caller ID als Environment Variable CALLER_ID gesetzt. Man kann sie also in einem Perl Script ganz einfach auslesen:

#!/usr/bin/perl
use env qw(CALLER_ID);
print "Caller ID is $CALLER_ID\n";

So weit so gut!

Man kann jetzt zwar auf eine PIN reagieren und sie auch nur von bestimmten Telefonnummern zu lassen, aber wenn ich bei mir zu Hause anrufe und mein Modem abhebt, dann ist die Leitung besetzt! Wie bekomme ich die Leitung jetzt wieder frei und kann meinem Rechner sagen, daß er sich ins Internet einwählen und mir seine IP Nummer per Mail / SMS schicken soll?

Ich hatte Probleme dies aus dem Caller Script heraus zu erledigen, obwohl ich mittels sleep ein paar Sekunden gewartet habe, bis vgetty das Modem wieder initialisiert hatte und das Device frei war, deswegen habe ich es ein wenig anders gehandhabt.

Die Sourcen zu meinem verwendeten Perl Script kann man am Ende dieses Artikels finden.

Ich habe deshalb das Caller Script und das Perl Script, was auf die Eingaben von verschiedenen PIN Nummern reagiert getrennt. Die beiden Programme kommunizieren über eine Lock Datei /var/lock/modemcontrol.lock und je nachdem, ob die richtige bzw. welche richtige PIN eingegeben wurde, steht halt ein anderer Wert in der Lock Datei.

Das zweite Perl Script (looklock.pl) wird mittels crontab jede Minute ausgeführt, schaut dann in diese Lock Datei, wertet sie aus und reagiet darauf. Wenn es jemandem gelingt eine bessere Lösung zu finden, bin ich natürlich ganz Ohr, aber so funktioniert es auf jeden Fall auch genauso wie ich es mir vorgestellt habe.

Und ganz nebenbei: Wie reagiert eigentlich das Caller Script wenn eine falsche PIN eingegeben wurde?

Anfangs hab ich das Modem sofort auflegen lassen, sobald eine falsche Taste gedrückt wurde, bis mich ein Freund darauf hingewiesen hat, daß man so sehr einfach die PIN durch probieren kann und man erfährt sogar die Länge der PIN! Tja... War echt nicht so ne tolle Idee...

In der neuen Version legt das Programm allerdings nur auf, wenn die (oder eine) richtige PIN eingegeben wurde. Wird eine falsche PIN eingegeben, kann man sich tot tippen die Verbindung bleibt bestehen.

Schnell noch eine kurze Erklärung, wie man mit RMD Dateien um zu gehen hat. Wenn man jetzt einen Sound vom Modem aufgenommen hat bzw. eine WAV Datei über das Modem als Begrüßung abspielen möchte, dann muß man diese Datei erstmal in ein für das Modem verständliches Format konvertieren werden.

Vgetty bringt dazu die Tools gleich mit.

Über wavtopvf WAV-file | pvftormd > rmdfile kann man eine WAV Datei in eine rmd Datei konvertieren.

Oder Mittels: rmdtopvf rmd-file | pvftowav > wavfile eine rmd Datei in eine WAV Datei.

Dabei wählt das Programm immer die optimale (höchste) Bitrate zum konvertieren aus. Man kann allerdings auch über die Option -s den Speed angeben, falls das Modem wie bei mir nur eine bestimmt Geschwindigkeit der Sounddateien unterstützt

. Aber jetzt gehts ab in die Sourcen der beiden Perl Scripte!!!

---:[ Modemcontrol.pl  --  Ein ungewöhnlicher Anrufbeantworter
#!/usr/bin/perl
#
# ---:[ Modemcontrol -- Playing around with a voice modem
#
# ---:[ Programmed by Bastian Ballmann
#
###[ Loading modules ]###
use Modem::Vgetty;
$Modem::Vgetty::Testing = 1;
###[ Configuration ]###
# PIN
$pin = "66666";
# PIN 2
$pin2 = "775566";
# Logfile
$logfile = "/var/log/modemcontrol.log";
# Sound zur Begruessung
$greet = "/var/spool/voice/messages/greet/tach.rmd";
# In welchem Ordner werden neue Gespraeche abgelegt?
$speechdir = "/var/spool/voice/incoming";
# Timeout in Sekunden
$timeout = 300;
#
###[ MAIN PART ]###
#
# Zerlege die PIN Nummern
@pin = split(//,$pin);
@pin2 = split(//,$pin2);
# Welche PIN wird gerade eingegeben?
$current_pin = 000;
@current_pin = ();
$pin_id = 0;
# Dateiname fuer aufgezeichnete Gespraeche
my $num = 0;
$num++ while(-r "$speechdir/$num.rmd");
$speechfile = "$num.rmd";
# Welcher Tach is denn heut?
$logtime = localtime(time);
# Count right and wrong DTMF tones
$counter = 0;
$failed = 0;
# Eingegebene PIN
$dtmf_string = "";
# Mail Array
@mail = ();
# Lege Vgetty Objekt an
my $modem = new Modem::Vgetty;
# Beim Alarm Signal, Record abbrechen
local $SIG{ALRM} = sub { $modem->stop; };
# Stoppe automatisch das Abspielen der Sounds, wenn der Anrufer
# eine Taste drueckt
$modem->autostop(qw(ON));
# Reagiere auf DTMF Toene, Stille und Busy Ton
$modem->add_handler('RECEIVED_DTMF',"pin",\&read_dtmf);
$modem->add_handler('BUSY_TONE',"killme", \&killme);
$modem->enable_events;
# Anrufer begruessen! =)
$modem->play_and_wait($greet);
$modem->play_and_wait($greet2);
$modem->beep(150,100);
$modem->beep(150,100);
$modem->beep(150,100);
$modem->waitfor('READY');
# Warte die Länge des PIN eingegeben wurde
while($counter != length($current_pin))
{
# Solange keine PIN Eingabe erfolgt, versuchen wir mal das
# Gespraech auf zu zeichnen
$modem->record("$speechdir/$speechfile");
alarm $timeout;
$modem->waitfor('READY');
}
# PIN is OK
if($counter == length($current_pin))
{
push @mail, "Received PIN $pin_id!!!";
&write_log("Received PIN $pin_id!!!");
&write_lock($pin_id);
}
# Der Anrufer hat nicht versucht eine PIN einzugeben
if(($counter < length($pin)) && ($failed == 0))
{
&killme;
}
#
###[ Subroutines ]###
# Read DTMF input
sub read_dtmf
{
my ($self,$input,$dtmf) = @_;
$self->stop;
&write_log("Received DTMF! $dtmf");
# Der Anrufer gibt die erste Zahl der PIN ein
if($current_pin == 000)
{
if($dtmf == $pin[$counter])
{
$counter++;
$pin_id = 1;
$current_pin = $pin;
@current_pin = @pin;
}
elsif($dtmf == $pin2[$counter])
{
$counter++;
$pin_id = 2;
$current_pin = $pin2;
@current_pin = @pin2;
}
else
{
$failed++;
}
}
else
{
if($dtmf == $current_pin[$counter])
{
$counter++;
}
else
{
$failed++;
}
}
$dtmf_string .= $dtmf;
}
# Write the log file
sub write_log
{
my $msg = shift;
push @mail,$msg;
open(LOG,">>$logfile");
print LOG "$logtime   $msg\n";
close(LOG);
}
# Mail to root and exit program
sub killme
{
$mail = join / /, @mail;
system"echo $mail | mail root";
exit(0);
}
sub write_lock
{
my $code = shift;
open(LOCK,">/var/lock/modemcontrol");
print LOCK "$code\n";
close(LOCK);
}

---:[ Looklock.pl -- Something to do?

#!/usr/bin/perl
#
# Wenn die PIN, die ein Anrufer eingegeben hat richtig war,
# ist der Wert in /var/lock/modemcontrol != 0
# Dann soll eine Aktion ausgefuehrt werden!
#
###[ Loading modules ]###
use IO::Socket;
#
###[ Configuration ]###
# Logfile
my $logfile = "/var/log/modemcontrol.log";
# Mailserver
$mailserver = "smtp.wtf.de";
# MAIL FROM
$mail_from = "bytebeater\@crazydj.de";
# MAIL TO
$mail_to = "bytebeater\@crazydj.de";
# Subject
$subject = "Crazymodem is online! =)";
#
###[ MAIN PART ]###
#
$logtime = localtime(time);
my $wvdial = 0;
# Read lock file
open(LOCK,"</var/lock/modemcontrol");
@lock = <LOCK>;
close(LOCK);
# Richtige PIN eingegeben?
# Starte wvdial
if($lock[0] == 1)
{
open(LOCK,">/var/lock/modemcontrol");
print LOCK "0";
close(LOCK);
&connect();
}
# Rufe eine bestimmte Telefonnummer an
elsif($lock[0] == 2)
{
open(LOCK,">/var/lock/modemcontrol");
print LOCK "0";
close(LOCK);
&dial_number;
}
#
###[ Subroutines ]###
#
sub connect
{
if($wvdial == 0)
{
$wvdial = 1;
# Starte Child Prozess
&mkchild('wvdial');
&write_log("Starting wvdial");
}
# Lese die Prozess Tabelle ein und warte bis pppd gestartet wurde
$pppd = 0;
&write_log('Waiting for pppd process');
while($pppd == 0)
{
open(PS,"ps -e|");
while(<PS>)
{
if($_ =~ /pppd/)
{
$pppd = 1;
}
}
close(PS);
}
sleep 4;
@mail = ('I am online.');
$socket = new IO::Socket::INET(PeerAddr => $mailserver, PeerPort => '25', Proto => 'tcp') || &connect;
&write_log("Connected to $mailserver. Sending mail to $mail_to");
$echo = <$socket>;
&write_log($echo);
print $socket "HELO du.da\r\n";
$echo = <$socket>;
&write_log($echo);
print $socket "MAIL FROM: $mail_from\r\n";
$echo = <$socket>;
&write_log($echo);
print $socket "RCPT TO: $mail_to\r\n";
$echo = <$socket>;
&write_log($echo);
print $socket "DATA\r\n";
$echo = <$socket>;
&write_log($echo);
print $socket "From: $mail_from\r\n";
print $socket "To: $mail_to\r\n";
print $socket "Subject: $subject\r\n";
for(@mail)
{
print $socket "$_\r\n";
}
print $socket "Have phun!!! ;-D\r\n";
print $socket ".\r\n";
$echo = <$socket>;
&write_log($echo);
print $socket "QUIT\r\n";
$echo = <$socket>;
&write_log($echo);
close($socket);
#
###[ Subroutines ]###
#
# Dial a telephone number via wvdial
sub dial_number
{
&write_log('Rufe XY an! =)');
&mkchild('wvdial XY');
# Lasse 2x klingeln und lege auf
sleep 13;
system"killall wvdial";
}
# Create a child process
sub mkchild
{
my $command = shift;
my $options = shift;
# Fork the child
if($pid = fork())
{
return;
}
# Hierhin kommt nur der Child Prozess
# Starte wvdial
system"$command";
exit(0);
}
# Write the logfile
sub write_log
{
my $msg = shift;
open(LOG,">>$logfile");
print LOG "$logtime   $msg\n";
close(LOG);