From adffdad3018f499153583dd93576e35da9a5fb75 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 05:14:36 +0000 Subject: [PATCH] AOL callers: play the client's network, then join the line to the AOL host The classic AOL client's CCL script answers a network's prompts before it speaks P3. With [aol] enabled, modemmenu plays AOLnet (UQKT1, aol, CONNECTED), SprintNet (@D, TERMINAL=, D1, @, par? 11, C address, CONNECTED) or Tymnet (A, LOG IN:, +, CALL CONNECTED), as AOL 3.0's own AOLNET.CCL, SPRINT.CCL and TYMNET.CCL wait for them, or takes P3 at once, then pipes the line to [aol] host (the BBS's AOL front end). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01B82eRRm1WrfJdB863kw36V Signed-off-by: Claude --- README.md | 1 + cmd/modemmenu/aol.go | 252 ++++++++++++++++++++++++++++++++++ cmd/modemmenu/aol_test.go | 155 +++++++++++++++++++++ cmd/modemmenu/main.go | 26 ++++ cmd/modemmenu/settings.go | 4 + docs/README.md | 1 + docs/aol.md | 42 ++++++ docs/reference.md | 13 ++ mkdocs.yml | 1 + samples/modemmenu.conf.sample | 20 +++ 10 files changed, 515 insertions(+) create mode 100644 cmd/modemmenu/aol.go create mode 100644 cmd/modemmenu/aol_test.go create mode 100644 docs/aol.md diff --git a/README.md b/README.md index 82be60d..4ad4699 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Selection: ppp - Logins from a users file, LDAP, SQL (MySQL/MariaDB, PostgreSQL, SQLite) or system accounts through PAM - PPP dialers detected (AutoPPP and PAP), with a pppd plugin +- The classic AOL client dialing in: AOLnet, SprintNet and Tymnet played, then the line to a BBS's AOL host - Menus with access by user and group, guests and self sign-up - Each item starts with the caller's credentials and the call's details: dialed number, caller ID, connect rate, time - Drop files for doors, last callers, and handing a caller to another menu diff --git a/cmd/modemmenu/aol.go b/cmd/modemmenu/aol.go new file mode 100644 index 0000000..778c69f --- /dev/null +++ b/cmd/modemmenu/aol.go @@ -0,0 +1,252 @@ +// Copyright (C) 2026 D-Modem Authors +// SPDX-License-Identifier: AGPL-3.0-only + +package main + +import ( + "errors" + "fmt" + "net" + "strings" + "time" + + "github.com/D-Modem/menu/internal/config" + "github.com/D-Modem/menu/internal/term" + "golang.org/x/sys/unix" +) + +// AOL: the classic AOL client (2.5 to 4.0) dials a network (AOLnet, +// SprintNet, Tymnet), whose prompts its CCL script answers, and then +// speaks P3 to the AOL host. modemmenu plays the network: the script's +// answer at the login prompt tells which one it expects, and once the +// network part is done the line goes to the AOL host ([aol] host, the BBS's +// AOL front end), where the caller signs on with their BBS account. +// +// AOLnet modemmenu says UQKT1 (before the first login prompt), the +// script answers "aol", modemmenu says CONNECTED +// SprintNet the script sends "@D" until it is asked TERMINAL=, answers +// D1, is given the "@" prompt, asks "par? 11" (the line's +// speed) and calls the host with "C
": CONNECTED +// Tymnet the script sends "A" (no line end) until it is asked to log +// in, logs in, is given "+", names the host: CALL CONNECTED +// P3 a script of one's own that starts P3 at once: its first +// packet (0x5A) where the name goes +// +// The strings are those AOL 3.0's own AOLNET.CCL, SPRINT.CCL and +// TYMNET.CCL wait for. + +// errAOL: an AOL client's network script, or its P3, instead of a name +type errAOL struct{ network string } + +func (e errAOL) Error() string { return "AOL client (" + e.network + ") at the login prompt" } + +// The networks modemmenu can play. +const ( + netAOLnet = "aolnet" + netSprintNet = "sprintnet" + netTymnet = "tymnet" + netP3 = "p3" +) + +// aolSettings: [aol] +type aolSettings struct { + enabled bool + host string // the AOL host, host:port + networks map[string]bool // the networks played + prompt string // AOLnet's greeting, before the first login prompt + speed string // SprintNet's answer to par? 11 +} + +// loadAOL: [aol] +func loadAOL(sec *config.Section) (aolSettings, error) { + var err error + a := aolSettings{host: sec.Get("host", "localhost:5190"), prompt: config.Unescape(sec.Get("aolnet_prompt", "UQKT1")), + speed: sec.Get("sprintnet_speed", "11:14"), networks: map[string]bool{}} + if a.enabled, err = sec.Bool("enabled", false); err != nil { + return a, err + } + for _, n := range config.Fields(strings.ToLower(sec.Get("networks", "aolnet sprintnet tymnet p3"))) { + switch n { + case netAOLnet, netSprintNet, netTymnet, netP3: + a.networks[n] = true + default: + return a, fmt.Errorf("[aol] networks: no network %q (aolnet, sprintnet, tymnet, p3)", n) + } + } + if _, _, err := net.SplitHostPort(a.host); a.enabled && err != nil { + return a, fmt.Errorf("[aol] host: %v", err) + } + return a, nil +} + +// sniffAOL: at the login prompt, a Tymnet script's lone "A" or a P3 +// packet; what is neither is put back for the name +func sniffAOL(t *term.Term, a aolSettings, deadline time.Time) (string, error) { + c, err := t.ReadOne(time.Until(deadline)) + if err != nil { + return "", err + } + switch { + case c == 0x5A && a.networks[netP3]: + t.Unread(c) + return netP3, nil + case c == 'A' && a.networks[netTymnet]: + // a caller types on; the script waits seconds for an answer + next, err := t.ReadOne(1500 * time.Millisecond) + if errors.Is(err, term.ErrTimeout) { + return netTymnet, nil + } + if err != nil { + return "", err + } + t.Unread(c, next) + return "", nil + } + t.Unread(c) + return "", nil +} + +// aolName: the network whose script answered the login prompt so, or "" +func aolName(a aolSettings, name string) string { + switch n := strings.ToUpper(strings.TrimSpace(name)); { + case n == "AOL" && a.networks[netAOLnet]: + return netAOLnet + case (n == "@D" || n == "D") && a.networks[netSprintNet]: + return netSprintNet + } + return "" +} + +// aolCall: the rest of the network's part, then the line to the AOL host +// until either ends; the exit status +func aolCall(t *term.Term, s *settings, sess *session, network string) int { + logf("AOL client (%s) at the login prompt %v", network, sess) + if err := playNetwork(t, s.aol, network); err != nil { + logf("aol: %s: %v %v", network, err, sess) + return 1 + } + c, err := net.DialTimeout("tcp", s.aol.host, 10*time.Second) + if err != nil { + logf("aol: %s: %v", s.aol.host, err) + t.WriteRaw("\r\nNOT AVAILABLE\r\n") // the scripts' word for a host that is down + return 1 + } + defer c.Close() + logf("aol: to %s %v", s.aol.host, sess) + start := time.Now() + n := pipe(t, c) + logf("aol: ended after %s, %d bytes from the caller %v", time.Since(start).Round(time.Second), n, sess) + return 0 +} + +// playNetwork: the network's prompts, as its script waits for them +func playNetwork(t *term.Term, a aolSettings, network string) error { + deadline := time.Now().Add(time.Minute) + line := func() (string, error) { + l, err := t.ReadLine(deadline, 80, true, "") + return strings.TrimSpace(l), err + } + switch network { + case netAOLnet: + return t.WriteRaw("\r\nCONNECTED\r\n") + case netSprintNet: + if err := t.WriteRaw("\r\nTERMINAL="); err != nil { + return err + } + if _, err := line(); err != nil { // D1 + return err + } + if err := t.WriteRaw("\r\n@"); err != nil { + return err + } + for { + l, err := line() + if err != nil { + return err + } + switch u := strings.ToUpper(l); { + case strings.HasPrefix(u, "PAR?"): + // the line's speed, as X.3 parameter 11 (14: 9600 and up) + if err := t.WriteRaw("\r\nPAR " + a.speed + "\r\n\r\n@"); err != nil { + return err + } + case strings.HasPrefix(u, "C "): + return t.WriteRaw("\r\n" + strings.TrimSpace(l[2:]) + " CONNECTED\r\n") + default: + if err := t.WriteRaw("\r\n@"); err != nil { + return err + } + } + } + case netTymnet: + if err := t.WriteRaw("\r\nPLEASE LOG IN: "); err != nil { + return err + } + if _, err := line(); err != nil { // name;password + return err + } + if err := t.WriteRaw("\r\n+"); err != nil { + return err + } + if _, err := line(); err != nil { // the host's name + return err + } + return t.WriteRaw("\r\nCALL CONNECTED\r\n") + } + return nil // P3: nothing to play +} + +// pipe: the line and c joined, what was read and put back going first, +// until either ends; the bytes from the caller +func pipe(t *term.Term, c net.Conn) int64 { + var n int64 + if p := t.TakePending(); len(p) > 0 { + if _, err := c.Write(p); err != nil { + return 0 + } + n += int64(len(p)) + } + done := make(chan struct{}) + go func() { + defer close(done) + buf := make([]byte, 4096) + for { + k, err := c.Read(buf) + if k > 0 && t.WriteRaw(string(buf[:k])) != nil { + return + } + if err != nil { + return + } + } + }() + buf := make([]byte, 4096) + for { + select { + case <-done: + return n + default: + } + ok, err := term.PollOne(t.In, unix.POLLIN, 500*time.Millisecond) + if err != nil { + c.Close() + return n + } + if !ok { + continue + } + k, err := unix.Read(t.In, buf) + if err == unix.EINTR || err == unix.EAGAIN { + continue + } + if k <= 0 || err != nil { + c.Close() // the caller hung up + <-done + return n + } + n += int64(k) + if _, err := c.Write(buf[:k]); err != nil { + return n + } + } +} diff --git a/cmd/modemmenu/aol_test.go b/cmd/modemmenu/aol_test.go new file mode 100644 index 0000000..044d035 --- /dev/null +++ b/cmd/modemmenu/aol_test.go @@ -0,0 +1,155 @@ +// Copyright (C) 2026 D-Modem Authors +// SPDX-License-Identifier: AGPL-3.0-only + +package main + +import ( + "fmt" + "net" + "os" + "path/filepath" + "strings" + "testing" +) + +// an AOL client's network scripts at the login prompt: modemmenu plays +// AOLnet, SprintNet and Tymnet as AOL 3.0's CCL files wait for them, and +// then joins the line to the AOL host +func TestAOLLine(t *testing.T) { + dir := t.TempDir() + bin := filepath.Join(dir, "modemmenu") + if out, err := runGo("build", "-o", bin, "."); err != nil { + t.Fatalf("build: %v\n%s", err, out) + } + // the AOL host: answers what the line sends it + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + go func() { + for { + c, err := ln.Accept() + if err != nil { + return + } + go func() { + defer c.Close() + // a P3 packet's first 8 bytes (it may come in pieces) + var got []byte + buf := make([]byte, 64) + for len(got) < 8 { + n, err := c.Read(buf) + got = append(got, buf[:n]...) + if err != nil { + break + } + } + fmt.Fprintf(c, "host got %q", got) + }() + } + }() + users := filepath.Join(dir, "users") + os.WriteFile(users, []byte("alice:{PLAIN}secret\n"), 0o600) + log := filepath.Join(dir, "log") + conf := filepath.Join(dir, "c.conf") + os.WriteFile(conf, []byte(fmt.Sprintf(` +[general] +log = %s +auth = file +[file] +path = %s +[aol] +enabled = yes +host = %s +[item info] +command = true +`, log, users, ln.Addr())), 0o600) + + p3 := "\x5a\x00\x00\x00\x34\x7f\x7f\xa3" + // AOLnet + c, cmd := callAs(t, bin, conf) + c.expect("UQKT1\r\n") + c.expect("login: ") + c.send("aol\r") + c.expect("CONNECTED\r\n") + c.send(p3) + c.expect(fmt.Sprintf("host got %q", p3)) + waitExit(t, cmd) + + // SprintNet + c, cmd = callAs(t, bin, conf) + c.expect("login: ") + c.send("@D\r") + c.expect("TERMINAL=") + c.send("D1\r") + c.expect("\r\n@") + c.send("par? 11\r") + c.expect("11:14") + c.send("C 31270\r") + c.expect("31270 CONNECTED\r\n") + c.send(p3) + c.expect("host got") + waitExit(t, cmd) + + // Tymnet: "A" with no line end + c, cmd = callAs(t, bin, conf) + c.expect("login: ") + c.send("A") + c.expect("LOG IN: ") + c.send("aol;secret\r") + c.expect("+") + c.send("AOL\r") + c.expect("CALL CONNECTED\r\n") + c.send(p3) + c.expect("host got") + waitExit(t, cmd) + + // a CCL of one's own: P3 at once + c, cmd = callAs(t, bin, conf) + c.expect("login: ") + c.send(p3) + c.expect(fmt.Sprintf("host got %q", p3)) + waitExit(t, cmd) + + // a caller whose name starts with A is a caller + c, cmd = callAs(t, bin, conf) + c.expect("login: ") + c.send("alice\r") + c.expect("Password: ") + c.send("secret\r") + c.expect("Selection: ") + cmd.Process.Kill() + + b, _ := os.ReadFile(log) + for _, w := range []string{"AOL client (aolnet)", "AOL client (sprintnet)", "AOL client (tymnet)", "AOL client (p3)", + "aol: to " + ln.Addr().String(), "login user=alice"} { + if !strings.Contains(string(b), w) { + t.Errorf("no %q in the log:\n%s", w, b) + } + } +} + +// [aol] networks takes only those modemmenu plays +func TestAOLSettings(t *testing.T) { + dir := t.TempDir() + conf := filepath.Join(dir, "c.conf") + users := filepath.Join(dir, "users") + os.WriteFile(users, nil, 0o600) + head := "[general]\nlog = none\nauth = file\n[file]\npath = " + users + "\n[item x]\ncommand = true\n" + os.WriteFile(conf, []byte(head+"[aol]\nenabled = yes\nnetworks = aolnet compuserve\n"), 0o600) + if _, err := loadSettings(conf); err == nil || !strings.Contains(err.Error(), "compuserve") { + t.Errorf("compuserve: %v", err) + } + os.WriteFile(conf, []byte(head+"[aol]\nenabled = yes\nnetworks = tymnet\n"), 0o600) + s, err := loadSettings(conf) + if err != nil { + t.Fatal(err) + } + if !s.aol.networks[netTymnet] || s.aol.networks[netAOLnet] || s.aol.host != "localhost:5190" { + t.Errorf("%+v", s.aol) + } + if aolName(s.aol, "aol") != "" { + t.Error("aol without AOLnet played") + } +} diff --git a/cmd/modemmenu/main.go b/cmd/modemmenu/main.go index ef9c5bc..a418bfd 100644 --- a/cmd/modemmenu/main.go +++ b/cmd/modemmenu/main.go @@ -220,6 +220,10 @@ func run(t *term.Term, s *settings, sess *session) int { if errors.Is(err, errAutoPPP) { return autoPPP(t, s, sess) } + var aol errAOL + if errors.As(err, &aol) { + return aolCall(t, s, sess, aol.network) + } if err != nil { if !errors.Is(err, term.ErrHangup) { t.Write("\r\n") @@ -280,6 +284,12 @@ func login(t *term.Term, s *settings, sess *session) error { deadline := time.Now().Add(s.loginTimeout) // a terminal not known by the end is not a Commodore defer func() { t.WatchPETSCII = false }() + if s.aol.enabled && s.aol.networks[netAOLnet] && s.aol.prompt != "" { + // AOLnet's greeting, which an AOL client's script waits for + if err := t.Write(s.aol.prompt + "\r\n"); err != nil { + return err + } + } for try := 1; try <= s.attempts; try++ { if err := t.Write(s.loginPrompt); err != nil { return err @@ -294,6 +304,16 @@ func login(t *term.Term, s *settings, sess *session) error { return errAutoPPP } } + if s.aol.enabled { + // an AOL client's Tymnet script, or its P3 + network, err := sniffAOL(t, s.aol, deadline) + if err != nil { + return err + } + if network != "" { + return errAOL{network} + } + } name, err := t.ReadLine(deadline, s.maxInput, true, "") if errors.Is(err, term.ErrPETSCII) { // a Commodore's key: the prompt again, for it @@ -314,6 +334,12 @@ func login(t *term.Term, s *settings, sess *session) error { try-- continue } + if s.aol.enabled { + // an AOL client's AOLnet or SprintNet script + if network := aolName(s.aol, name); network != "" { + return errAOL{network} + } + } if s.guest.enabled && strings.EqualFold(name, s.guest.name) { // no password for a guest sess.User, sess.Auth = s.guest.name, "guest" diff --git a/cmd/modemmenu/settings.go b/cmd/modemmenu/settings.go index 5fd55b6..2bf660e 100644 --- a/cmd/modemmenu/settings.go +++ b/cmd/modemmenu/settings.go @@ -92,6 +92,7 @@ type settings struct { dropfile dropfileSettings autoppp autopppSettings pppPool pppPoolSettings + aol aolSettings terminal terminalSettings authd *authdSettings // nil: no auth socket menuTitle string @@ -189,6 +190,9 @@ func loadSettings(path string) (*settings, error) { if s.pppPool, err = loadPPPPool(c.Section("ppp_pool")); err != nil { return nil, err } + if s.aol, err = loadAOL(c.Section("aol")); err != nil { + return nil, err + } if s.terminal, err = loadTerminal(c.Section("terminal")); err != nil { return nil, err } diff --git a/docs/README.md b/docs/README.md index 0ad03ac..8dd3eb6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,7 @@ - [Configuration reference](reference.md): every section and key, from the sample - [Authentication](authentication.md): users file, LDAP, SQL, system accounts (PAM), changing passwords, the auth socket - [PPP dialers](ppp.md): AutoPPP and PAP, the pppd plugin +- [AOL callers](aol.md): the classic AOL client dialing in, its network played - [Security notes](security.md) - [Tests](testing.md) diff --git a/docs/aol.md b/docs/aol.md new file mode 100644 index 0000000..68b1c55 --- /dev/null +++ b/docs/aol.md @@ -0,0 +1,42 @@ +# AOL callers + +## The classic AOL client, dialing in + +AOL's own client (2.5 to 4.0) never types at a login prompt. It dials a +network, AOLnet, SprintNet or Tymnet, and its CCL script answers that +network's prompts. Then it speaks P3, AOL's own protocol, to the AOL host. +With `[aol] enabled = yes`, modemmenu plays the network and then joins the +line to `host`. That is [D-Modem/bbs](https://github.com/D-Modem/bbs)'s +AOL front end (`[aol]` there), where the caller signs on with their BBS +name and password. + +``` +[aol] +enabled = yes +host = localhost:5190 +``` + +modemmenu tells which network the client expects from what its script +does at the login prompt. The strings are the ones AOL 3.0's +`AOLNET.CCL`, `SPRINT.CCL` and `TYMNET.CCL` wait for. + +| Network | The script | modemmenu | +|---|---|---| +| AOLnet | waits for `UQKT1`, answers `aol` | says `UQKT1` before the first login prompt, then `CONNECTED` | +| SprintNet | sends `@D` | asks `TERMINAL=`, gives the `@` prompt, answers `par? 11` with the line's speed, and `C
` with `
CONNECTED` | +| Tymnet | sends `A`, with no line end | asks `PLEASE LOG IN:`, answers the login with `+` and the host's name with `CALL CONNECTED` | +| P3 | a script of one's own that starts P3 at once | joins the line to the host at the first packet | + +In the client's Setup, add the D-Modem line's phone number as an access +number of the network to play, and pick it. + +`networks` limits which of these modemmenu plays. Leave out `aolnet` to +keep its `UQKT1` line off every caller's screen. With AOLnet or SprintNet +played, the names `aol` and `@D` at the login prompt are the client's, +not a user's. A caller's name beginning with `A` is still a name, because +a person types on while the Tymnet script waits seconds for an answer. + +The call is logged (`AOL client (sprintnet) at the login prompt`, then +`aol: to localhost:5190`, then how long it lasted). modemmenu's own +logins, time limits and menus don't apply to it: the BBS signs the caller +on and keeps its own limits. diff --git a/docs/reference.md b/docs/reference.md index a1abc64..20f9a93 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -25,6 +25,7 @@ full. | [`[authd]`](#authd) | The auth socket: the BBS's other logins (telnet, SSH, the web) ask the backends above for a user's password through it, so that it is the same one. | | [`[ppp_pool]`](#ppp_pool) | The PPP callers' addresses: a command that names {ppp_remote} (or {ppp_local}, {ppp_dns}, {ppp_dns2}) takes the first free address of range for the call, so that several lines run PPP at once, and gives it back when pppd ends. | | [`[autoppp]`](#autoppp) | AutoPPP: a dialer that starts PPP at once (Windows' Dial-Up Networking, most others) sends a PPP frame where the login prompt wants a name; then command runs on the line instead (pppd), and its modemmenu.so plugin (make builds it with ppp-dev's headers; make install puts it in pppd's plugin directory) asks this call's auth socket for the PAP login: the backends check it as at the prompt, then item's access rules, then the time limits, whose time left becomes pppd's maxconnect. | +| [`[aol]`](#aol) | AOL: the classic AOL client (2.5 to 4.0) dials a network and its CCL script answers the network's prompts before it speaks P3. | | [`[dropfile]`](#dropfile) | drop files for doors (an exec item's dropfile =): what they say of the BBS, and how the door reaches the caller. | | [`[menu]`](#menu) | The menu: its title, prompt, items and the words that hang up. | | [`[item NAME]`](#item-name) | A menu item: a program on the line, a server to connect the caller to, or a built-in (see the item keys below). | @@ -221,6 +222,18 @@ AutoPPP: a dialer that starts PPP at once (Windows' Dial-Up Networking, most oth | `command` | `exec /usr/sbin/pppd nodetach lock passive local {ppp_local}:{ppp_remote} ms-dns {ppp_dns} ms-dns {ppp_dns2} require-pap refuse-chap refuse-mschap refuse-mschap-v2 refuse-eap plugin modemmenu.so ipparam autoppp` | See the section above. | | `item` | `ppp` | the access rules (groups, min_baud, callerids...) a PPP login must pass. | +## `[aol]` + +AOL: the classic AOL client (2.5 to 4.0) dials a network and its CCL script answers the network's prompts before it speaks P3. modemmenu plays the network (AOLnet, SprintNet, Tymnet, as AOL 3.0's scripts wait for them), then joins the line to host: the BBS's AOL front end, where the caller signs on with their BBS account. AOLnet needs its greeting (aolnet_prompt) said before the first login prompt, which every caller sees; and the name "aol" (AOLnet's answer) and "@D" (SprintNet's) are then the client's, not a user's. + +| key | sample | what it does | +|---|---|---| +| `enabled` | `no` | See the section above. | +| `host` | `localhost:5190` | See the section above. | +| `networks` | `aolnet sprintnet tymnet p3` | which to play: aolnet sprintnet tymnet, and p3 (a CCL of one's own that starts P3 at once) | +| `aolnet_prompt` | `UQKT1` | AOLnet's greeting, which its script waits for. | +| `sprintnet_speed` | `11:14` | the answer to SprintNet's "par? 11", the line's speed: 11:14 is 9600 bps and up (the client then uses its high-speed settings) | + ## `[dropfile]` drop files for doors (an exec item's dropfile =): what they say of the BBS, and how the door reaches the caller. The defaults are what Synchronet writes for a door on stdin and stdout. diff --git a/mkdocs.yml b/mkdocs.yml index 56dd5f5..153caf4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,6 +19,7 @@ nav: - Authentication: authentication.md - PPP: ppp.md - Dial-up network: dialup-network.md + - AOL callers: aol.md - Security: security.md - Testing: testing.md - Documentation index: README.md diff --git a/samples/modemmenu.conf.sample b/samples/modemmenu.conf.sample index f845b3f..fafb264 100644 --- a/samples/modemmenu.conf.sample +++ b/samples/modemmenu.conf.sample @@ -284,6 +284,26 @@ command = exec /usr/sbin/pppd nodetach lock passive local {ppp_local}:{ppp_remot ; the access rules (groups, min_baud, callerids...) a PPP login must pass item = ppp +; AOL: the classic AOL client (2.5 to 4.0) dials a network and its CCL +; script answers the network's prompts before it speaks P3. modemmenu +; plays the network (AOLnet, SprintNet, Tymnet, as AOL 3.0's scripts wait +; for them), then joins the line to host: the BBS's AOL front end, where +; the caller signs on with their BBS account. AOLnet needs its greeting +; (aolnet_prompt) said before the first login prompt, which every caller +; sees; and the name "aol" (AOLnet's answer) and "@D" (SprintNet's) are +; then the client's, not a user's +[aol] +enabled = no +host = localhost:5190 +; which to play: aolnet sprintnet tymnet, and p3 (a CCL of one's own that +; starts P3 at once) +networks = aolnet sprintnet tymnet p3 +; AOLnet's greeting, which its script waits for +aolnet_prompt = UQKT1 +; the answer to SprintNet's "par? 11", the line's speed: 11:14 is 9600 bps +; and up (the client then uses its high-speed settings) +sprintnet_speed = 11:14 + ; drop files for doors (an exec item's dropfile =): what they say of the ; BBS, and how the door reaches the caller. The defaults are what ; Synchronet writes for a door on stdin and stdout