# Ikonboard 3.1.1 -- complete teardown A Perl CGI web forum, released July 2002 by Jarvis Entertainment Group, Inc. Four storage backends, server-side sessions, hashed passwords, member groups, a compiled skin engine and mod_perl support -- 73,000 lines of Perl trying to turn a category of hobbyist software into a real application, about eighteen months before PHP took the category away entirely. It is not a new version of Ikonboard 2. Compared line by line, the two share seventeen lines of source, all of them boilerplate. This is a full teardown of the distribution: the architecture, every module, every on-disk record format, the security model measured against both 2002 and now, the three upgrade paths that shipped and what they cost, who owned it and who wrote it -- and what this particular copy reveals about how the release was built, and about the last change anyone ever made to it. The distribution is preserved at . Written August 2026. Sources, uncertainties and acknowledgements are in section 12. --- ## Contents | | | |---|---| | 1 | [What it is](#1-what-it-is) -- the shape of the thing, and why this copy matters | | 2 | [History and authorship](#2-history-and-authorship) -- the handover to Jarvis, Matt Mecham's exit, the contributors, the licensing | | 3 | [Architecture](#3-architecture) -- one entry point, four database backends, templates compiled to Perl | | 4 | [Module reference](#4-module-reference) -- all 179 files, every endpoint, and the dead code | | 5 | [Data formats](#5-data-formats) -- 29 tables field by field, and the drift between backends | | 6 | [Security](#6-security) -- what improved, what the input filter misses, and what was left open | | 7 | [Installation and operation](#7-installation-and-operation) -- how you ran one of these in 2002 | | 8 | [Upgrade path](#8-upgrade-path) -- the three migrations, and what converting from 2.1.9 actually cost | | 9 | [Archaeology](#9-archaeology) -- the tarball clock, the copyright strata, and the last edit | | 10 | [How to reproduce this](#10-how-to-reproduce-this) -- the method | | 11 | [The toolchain](#11-the-toolchain) -- all nine analysis scripts in full | | 12 | [About this teardown](#12-about-this-teardown) -- uncertainties, acknowledgements, corrections | Sections 4, 5 and 6 are reference material and long. Sections 3, 8 and 9 are the ones to read if you only read three. --- ## 1. What it is Ikonboard 3.1.1 is a web forum written in Perl, released in July 2002 by Jarvis Entertainment Group, Inc. It ran as CGI on shared hosting, stored its data in any of four backends -- Berkeley DBM, MySQL, PostgreSQL or Oracle -- and put a bulletin board on the internet for anyone with FTP access and a `cgi-bin` directory. It is 179 Perl files and about 73,000 lines. Its immediate predecessor, Ikonboard 2.1.9, was 43 files and 15,586 lines. Compared line by line, ignoring comments and whitespace, they share seventeen lines -- a JavaScript clock, a few HTML table tags, an array of month names, and `print "Content-type: text/html\n\n";`. Version 3 is not a new version of Ikonboard 2. It is a different program with the same name. --- ### 1.1 The shape of the thing Version 2 was forty standalone CGI scripts that each read some text files and printed HTML. Version 3 is a single dispatcher and a library: ``` GET /cgi-bin/ikonboard.cgi?act=ST&t=42&s= | v +------------------------------------------------------------------+ | ikonboard.cgi the only executable page in the product | | | | require Boardinfo.cgi <- generated config, as Perl | | %iB::IN = map { _clean_key => _clean_value } CGI->param | | iDatabase::SQL->new( DB_DRIVER => 'DBM' | 'mySQL' | 'pgSQL' | ... )| | Sessions->authenticate | | $std->LoadSkin -> do Skin/Default/Universal.pm | | | | %Mode = ( ST => ['Topic','ShowTopic'], SF => ['Forum',...], ... ) | | 44 actions | | | | eval "require Topic; my $idx = Topic->new(); $idx->ShowTopic($db)"| +------------------------------------------------------------------+ | +--------------+--------------+ v v Sources/Topic.pm Skin/Default/TopicView.pm controller: reads the DB, view: subs that return decides what to show qq~ ...HTML... ~ | | +--------------+--------------+ v HTML response ``` Three things about that diagram are the whole design. **One entry point.** Everything is `ikonboard.cgi` with an `act=` parameter. The dispatch table is a literal hash, and an unrecognized `act` falls back to the board index rather than reaching the `eval`. A second stage inside each module dispatches again on `CODE=`; a third, `AD=1`, diverts the whole request into the admin control panel. **A database abstraction layer.** `Sources/iDatabase/` declares the schema once and implements it four times. An operator on a hosting plan with no database server got DBM files; an operator with MySQL got MySQL; the board code above the driver did not know or care. This is the single largest architectural change from version 2, and the reason the product could target both a hobbyist on a $5 host and a site with real traffic. A fifth driver, `Driver/CSV.pm`, ships in the tree and cannot load -- it declares the wrong package name, never inherits from the driver base class, and has no constructor, and the installer does not offer it. It is a 33 KB fossil of the previous storage engine, and it is the largest piece of dead code in the product. **Templates compiled to Perl.** A skin is a set of `.cfg` files holding HTML, which the admin control panel compiles into `.pm` files full of subs that `return qq~ ... ~`. The board loads only the compiled form. There is no template parser at runtime -- the template *is* Perl by the time it executes. --- ### 1.2 Why this copy is worth a teardown Two reasons, one about the software and one about this particular download. Ikonboard 3 is the point where the Perl-CGI forum tried to become a real application. It has server-side sessions, hashed passwords, member groups with permission masks, a database abstraction layer, a compiled skin engine, translatable strings, mod_perl support, and importers for rival forum software. Version 2 had none of those; it had plaintext passwords in flat files and English hardcoded into forty scripts. Version 3 is what the category looked like just before PHP and MySQL took it away entirely, and it is a better piece of engineering than its reputation suggests. It is also not an abstract artifact here. **ffoncrack.com ran this release**, and its member table survives in a personal backup -- a Berkeley DB hash file whose values are the pipe-delimited records specified in section 5. That board is the reason several statements in this document could be checked against a real installation rather than only against the code. Section 12.5 sets out what it settles. The second reason is that this distribution ships its source inside six tar archives, and a tar header preserves a modification time for every file it holds. Zip timestamps are unreliable; tar timestamps are epoch seconds and survive intact. So this download carries the vendor's own build clock, file by file, from a machine that stopped existing two decades ago -- and the tree was never installed, so nothing an operator did has overwritten it. That is enough to reconstruct how the release was actually made. Development ran in a nine-week burst through June and July 2002 and stopped on 07/15/2002. Four months later, on 11/24/2002, three of the six archives were rebuilt within fourteen minutes of each other. Exactly one file inside them postdates July: the admin control panel's navigation menu, dated 11/25/2002, edited by hand -- and the edit is confined to a block of promotional links pointing at the company's hosting business and its affiliate sites. The last change ever made to Ikonboard 3.1.1 was an advertisement. --- ### 1.3 What is in the box The download is a zip. Unpacked, it is 97 files: four HTML guides, a folder of installer screenshots, a small toolbox, three upgrade kits, and `Upload_Files/` -- which is what you FTP to the server. The Perl is inside six tarballs in `Upload_Files/cgi-bin/`, because untarring server-side avoided the era's single worst support problem, uploading source in the wrong FTP transfer mode. | Archive | Contents | Files | |---|---|---| | `Sources.tar` | the entire application | 118 | | `Skin.tar` | the Default skin, templates and compiled views | 61 | | `non-cgi.tar` | images, emoticons, avatars, CSS, mail templates | 262 | | `Languages.tar` | the English string tables | 33 | | `Database.tar` | 29 table declarations, one directory per table | 34 | | `Data.tar` | runtime data stubs, skin registry, MIME map | 6 | Everything in this teardown that discusses "the source tree" refers to those archives unpacked in place, which is what an operator would have had. That reconstruction is analysis work added here; it is not part of the download. The application itself divides roughly like this: | Subsystem | Files | Perl files | Perl lines | |---|---|---|---| | Admin control panel (`Sources/Admin/`) | 34 | 33 | 24,088 | | Front-end controllers (`Sources/*.pm`) | 33 | 32 | 16,629 | | Database abstraction (`Sources/iDatabase/`) | 13 | 12 | 6,530 | | Skin (templates + compiled views) | 61 | 29 | 6,566 | | Installer | 9 | 8 | 4,304 | | Core library (`Sources/Lib/`) | 5 | 4 | 2,906 | | User control panel and messenger | 6 | 5 | 2,528 | | Language packs | 33 | 29 | 2,344 | | Search subsystem | 6 | 6 | 1,683 | | Small feature modules (`Sources/Misc/`) | 12 | 11 | 1,323 | | Mail, SSI, mod_perl glue | 4 | 3 | 1,113 | | Bundled CPAN (Tar, Zlib, MIME) | 5 | 4 | 1,949 | | Board root (`ikonboard.cgi`, `installer.cgi`) | 7 | 2 | 838 | | Database, seed data, web root, runtime data | 322 | 1 | 4 | | **Total** | **550** | | **72,805** | Two numbers in that table are worth pausing on. The admin control panel is larger than the entire public-facing board -- 24,000 lines against 16,600 -- which is what happens when every setting gets its own hand-written form. And the single largest file in the product is `Sources/Admin/Options.pm` at 2,298 lines and 107 KB: the board settings screen, for a board with 118 configurable options. --- ### 1.4 What this document covers The chapters are independent; the ones worth reading if you only read three are the architecture chapter, the upgrade chapter, and the archaeology chapter. - **History and authorship** -- the handover from Ikonboard.com to Jarvis Entertainment Group, what the copyright headers prove about it, Matt Mecham's departure to Invision, the community contributors credited in the source, and the licensing change. - **Architecture** -- the dispatcher, the database abstraction layer, the skin engine, sessions, the language layer, and mod_perl. - **Data formats** -- all 29 tables field by field, the storage backends, the schema drift between them, the skin and language formats, and notes on reading the data today without Ikonboard. - **Module reference** -- every module, every endpoint, and the dead code. - **Security** -- what improved since 2.1.9, what the front-door input filter does and does not cover, and the findings, rated as they would have been in 2002 and as they read now. - **Installation and operation** -- what running one of these actually involved, and whether it still runs. - **Upgrade path** -- the three migrations that shipped, what the Ikonboard 2 converter carried across and what it dropped, and a feature-by-feature account of what changed between 2.1.9 and 3.1.1. - **Archaeology** -- what the timestamps, the tarballs and the copyright headers reveal about how this release was built and abandoned. Then the method, the analysis scripts in full, and a note on sources, uncertainties and corrections. Two conventions throughout. Citations are `file:line` against the unpacked tree. Anything quoted from the 2002 source or documentation is verbatim, including its British spellings and its typos, because the spelling is part of the evidence. --- ## 2. History and authorship Ikonboard 3.1.1 is a Perl CGI bulletin board released in 2002 and copyright Jarvis Entertainment Group, Inc. Its predecessor, Ikonboard 2.1.9 (June 2001), is copyright Ikonboard.com and states on every page of source that "All files written by Matthew Mecham." Between those two releases the product changed owners, changed countries, changed legal posture, changed programming language conventions, and changed development model -- from one author to a volunteer team working against a public bug tracker. It also lost the author. Almost all of that is legible in the shipped code. This chapter reads it. --- ### 2.0 How to read this chapter The teardown tree is an unpacked distribution that was **never installed** (see `out_provenance.txt` section 2.0: no `Boardinfo.cgi`, no `install.lock`, no `.pwd` key file, no database rows). That is unusually good for archival purposes. It means every file modification time in the tree is the *vendor's* clock -- the timestamps a developer's editor and the packaging machine wrote -- and not an operator's. Nobody CHMODed these files, nobody edited a skin, nobody's board ever ran on them. Timestamps quoted in this chapter are **UTC**, matching the convention used by the pre-computed analysis in `out_provenance.txt`. This matters at exactly one point: the final file, `Sources/Admin/Menuadmin.pm`, is `2002-11-25 05:05:08` UTC, which is late evening on 11/24/2002 in US Central time. Both readings appear in this document where relevant, labeled. Every claim below is tagged by what kind of evidence supports it: | Tag | Meaning | |-----|---------| | **[CODE]** | Proven by a file in `ib311/cgi-bin/` or `ib219/`. Cited as `file.pm:123`. | | **[DOC]** | Proven by a shipped document in the distribution (`ib311/`). | | **[ARCHIVE]** | Proven by a dated Internet Archive capture of a contemporaneous web page. Timestamp given. | | **[EXTERNAL]** | Secondary source -- encyclopedia, interview, CVE database, mailing list archive. | | **[INFERENCE]** | My reading of the above. Not a fact. Flagged in the text. | | **[UNVERIFIED]** | Stated in a source I could not corroborate, or a claim I could not confirm at all. | The established framing facts for the whole teardown -- which this chapter does not re-derive -- are: `$iB::VERSION = '3.1.1'` at `ikonboard.cgi:329`; the tree was never installed; development ended around 07/15/2002 with tarballs repackaged 11/24/2002 and one file dated 11/25/2002; and 3.1.1 is a rewrite rather than an evolution of 2.1.9, sharing only 17 normalized source lines (0.49% of 2.1.9), all of them boilerplate. --- ### 2.1 Two products, two owners Start with the bare ownership statements, side by side, as the two products make them. **Ikonboard 2.1.9**, header of every core file **[CODE]** (`ib219/cgi-bin/ikonboard.cgi:3-13`): ``` ############################################################# # Ikonboard v2.1 # Copyright 2001 Ikonboard.com - All Rights Reserved # Ikonboard is a trademark of Ikonboard.com # # Software Distributed by: Ikonboard.com # Visit us online at http://www.ikonboard.com # Email us on boards@ikonboard.com # # All files written by Matthew Mecham ############################################################# ``` **Ikonboard 3.1.1**, header of the dispatcher **[CODE]** (`ikonboard.cgi:5-20`): ``` ###################################################### #| Ikonboard v3.1 #| #| No parts of this script can be used outside Ikonboard #| without prior consent. #| You must keep this header intact and all copyright #| links visable. #| #| (c)2002 Jarvis Entertainment Group, Inc. #| Web: #| Email: ib@ikonboard.com #| IRC Server : irc.ikonboard.com #| Port : #6667 #| Channels : #ikonboard #help #support #| #| Please Read the licence for more information. ###################################################### ``` Four things changed and one did not. 1. The **copyright holder** changed from Ikonboard.com to Jarvis Entertainment Group, Inc. 2. The **author attribution disappeared**. 2.1.9 names Matthew Mecham in the header of every file. 3.1.1's dispatcher names nobody. 3. The **trademark assertion disappeared**. 2.1.9 says "Ikonboard is a trademark of Ikonboard.com." No file in 3.1.1 makes a trademark claim. (The license does not either; see section 2.6.) 4. A **license-enforcement instruction appeared** in the header itself: "You must keep this header intact and all copyright links visable." That is not decoration; it is restated in the license and mechanically enforced in the code (section 2.6.4). 5. The **domain did not change**. `www.ikonboard.com` remains the product's home, the support address remains `@ikonboard.com`, the IRC network remains `irc.ikonboard.com`. The brand survived the corporate transfer intact. Note the British spelling "visable" (sic -- for "visible") and "license" in the 3.1.1 header, both inherited from the 2.1.9-era house style. Ikonboard.com was based in Gosport, Hampshire; JEG was based in Texas. The misspelling propagated forward into Jarvis-copyright files unchanged, which is the first hint that the rebranding was a find-and-replace on a name rather than a rewrite of the header text. Quotations throughout this chapter preserve 2002 spelling and typography verbatim. --- ### 2.2 The handover, as the code records it #### 2.1 The copyright census The tree contains 179 Perl files (`.pm`, `.cgi`, `.pl`). Classifying each by the copyright line in its first 4 KB gives **[CODE]**: | Count | Copyright line | |------:|----------------| | 48 | `(c)2001 Jarvis Entertainment Group, Inc.` | | 38 | `(c)2001-2002 Jarvis Entertainment Group, Inc.` | | 3 | `(c)2002 Jarvis Entertainment Group, Inc.` | | 1 | `(c)2001 Ikonboard.com ` | | 1 | `(c)2001-2002 Ikonboard.com ` | | 88 | *(no copyright line at all)* | `out_provenance.txt` section 2.3 counts ten distinct header *variants* rather than six copyright *strings*, because it distinguishes the comment prefix (`#` vs `#|`) and preserves literal tab characters that got baked into some headers -- e.g. the two separate entries `# (c)2001-2002 Jarvis Entertainment Group, Inc.` and `# (c)2001-2002 Jarvis Entertainment\tGroup, Inc.` (`Sources/Admin/LangControl.pm`). Those tabs are themselves evidence and are discussed in section 2.5.7. Two observations from the census: **First, 88 of 179 files -- 49% of the shipped Perl -- carry no copyright notice whatsoever.** This includes the entire compiled skin (`Skin/Default/*.pm`, 30 files), eleven of the thirty language files, the whole vendored-third-party set (`Compress/Zlib.pm`, `Archive/Tar.pm`, `MIME/Base64.pm`, `Mail/Sendmail.pm`, `Lib/Crypt.pm`, `Lib/MD5.pm`), and -- significantly -- the entire `iDatabase/` layer and several first-party modules (`Sources/Profile.pm`, `Sources/LogInOut.pm`, `Sources/Warn.pm`, `Sources/Makelog.pm`, `Sources/iTextparser.pm`). For a product whose license makes retention of copyright headers a condition of use (section 2.6), half the codebase has no header to retain. **[INFERENCE]** This is what an incompletely executed branding pass looks like: someone walked the tree stamping headers onto files, and stopped -- or never started -- on whole subdirectories. **Second, two files still say Ikonboard.com.** #### 2.2 The two survivals `Sources/Sessions.pm:1-11` **[CODE]**: ```perl package Sessions; use strict; #+------------------------------------------------------------------------------------------------------ #| Ikonboard [ v3.0 ] #| #| No parts of this script can be used outside Ikonboard without prior consent. #| You must keep this header intact and all copyright links visable. #| (c)2001 Ikonboard.com #| #| Please Read the licence for more information. #+------------------------------------------------------------------------------------------------------ ``` `Sources/UserCP/Menu.pm:1-13` **[CODE]** (tabs preserved as they appear in the file): ```perl package UserCP::Menu; use strict; #+------------------------------------------------------------------------------------------------------ #| Ikonboard [ v3.1 ] #| #| No parts of this script can be used outside Ikonboard without prior consent. #| You must keep this header intact and all copyright links visable. #| (c)2001-2002 Ikonboard.com #| #| This is to generate the interface for the member profiles and everything in their control panel. #| #| Please Read the licence for more information. #+------------------------------------------------------------------------------------------------------ ``` These are the last two places in the shipped product that name the previous owner. They are worth reading closely, because the naive interpretation -- "these files were never touched after the handover" -- is **wrong**, and the correct interpretation is more interesting. Their modification times **[CODE]**: | File | mtime (UTC) | Version string in header | Copyright year in header | |------|-------------|--------------------------|--------------------------| | `Sources/Sessions.pm` | 2002-07-13 21:20:54 | `[ v3.0 ]` | `2001` | | `Sources/UserCP/Menu.pm` | 2002-06-24 19:10 | `[ v3.1 ]` | `2001-2002` | `Sessions.pm` is one of the last twenty-five files modified before development stopped (`out_provenance.txt` section 2.2 lists it third from the end of the 07/2002 run). It was being actively edited two days before the project went quiet. Its header nonetheless still says v3.0 and still says Ikonboard.com. `UserCP/Menu.pm` is stronger evidence still. Its version string **was** bumped from `v3.0` to `v3.1`, and its copyright year **was** extended from `2001` to `2001-2002` -- but the entity name was left as `Ikonboard.com`. Somebody ran an editing pass over this file that specifically updated the two numeric fields in the copyright header and did not update the name in between them. **[INFERENCE]** The survivals are not fossils of untouched code. They are fossils of an *incomplete substitution rule*. Whatever pass converted `Ikonboard.com` to `Jarvis Entertainment Group, Inc.` across the tree missed these two files, while a *different* and later pass -- a year-bump -- hit at least one of them. The stratigraphy that matters here is not "which files are old" but "which mechanical edits ran over which files, in what order." The pipeline that produced Ikonboard 3.1.1 applied several such passes, and at least one of them was broken (section 2.2.4). #### 2.3 Mecham's byline, still present The removal of Mecham's name from file headers was likewise incomplete, and in a way that establishes far more than the survivals do. Thirteen files in the tree carry an explicit `Author:` line **[CODE]**: | File | Author line | |------|-------------| | `Sources/iDatabase/SQL.pm:7` | `# Author: Matthew Mecham ` | | `Sources/iDatabase/Driver/Base.pm:7` | `# Author: Matthew Mecham ` | | `Sources/iDatabase/Driver/CSV.pm:9` | `# Author: Matthew Mecham ` | | `Sources/iDatabase/Driver/DBM.pm:7` | `# Driver Author: Matthew Mecham ` | | `Sources/iDatabase/Driver/mySQL.pm:7` | `# Driver Author: Matthew Mecham ` | | `Sources/iDatabase/Driver/Oracle.pm:7` | `# Driver Author: Andrey Prokopenko ` | | `Sources/iDatabase/Admin/a_base.pm:7` | `# Author: Matthew Mecham ` | | `Sources/iDatabase/Admin/a_DBM.pm:7` | `# Author: Matthew Mecham ` | | `Sources/iDatabase/Admin/a_mySQL.pm:7` | `# Author: Matthew Mecham ` | | `Sources/iDatabase/Admin/a_pgSQL.pm:7` | `# Author: Matthew Mecham ` | | `Sources/iDatabase/Admin/a_Oracle.pm:7` | `# Author: Matthew Mecham ` | | `Sources/iPerl/mod_perl.pm:3` | `# by: Matthew Mecham` | | `Sources/Admin/SQLclient.pm:18` | `# Script Author: Nurlan Mukhanov (Infection)` | | `Sources/Upgrade.pm:15` | `# Script Author: Phil Gengler (LrdChaos) ` | Eleven of those thirteen are Mecham's, and ten of them are the `iDatabase` layer -- the SQL abstraction that is the single largest architectural difference between 2.1.9 and 3.x (`out_delta.txt` section 2.4 lists `Sources/iDatabase/` first among features with no 2.1.9 counterpart). Every driver but the Oracle one is his. The base class is his. The admin-side schema handlers are his. And `Sources/iDatabase/Driver/CSV.pm:4-14` dates it **[CODE]**: ```perl ################################################################ # # iDatabase v1.0 (May 2001) # # Developed for Ikonboard. # Author: Matthew Mecham # # Accessor methods to databases # # CSV: Interface to text files. # ################################################################ ``` **iDatabase v1.0, May 2001.** An in-code date stamp, from the author, on the layer that defines Ikonboard 3's architecture. All five drivers still declare `$VERSION = 1.0` in July 2002 -- `Driver/CSV.pm:19`, `Driver/DBM.pm:29`, `Driver/mySQL.pm:26`, `Driver/Oracle.pm:25`, `Driver/pgSQL.pm:15`. Fourteen months of iB3 development did not produce an iDatabase 1.1. The installer's byline is even more explicit. `installer.cgi:4-21` **[CODE]**: ```perl #################################################### #| Ikonboard by Matthew Mecham [ v3.0 ] #| #| No parts of this script can be used outside Ikonboard #| without prior consent. #| You must keep this header intact and all copyright #| links visable. #| #| (c)2001 Jarvis Entertainment Group, Inc. #| Web: #| Email: ib@ikonboard.com #| IRC Server : irc.ikonboard.com #| Port : #6667 #| Channels : #ikonboard #help #support #| #| Please Read the licence for more information. #################################################### ``` "Ikonboard by Matthew Mecham [ v3.0 ]" over "(c)2001 Jarvis Entertainment Group, Inc." The authorship line and the ownership line sit two lines apart and disagree about who made the thing. This is the handover in a single header: the work is Mecham's, the copyright is Jarvis's, and nobody reconciled the two before shipping. One more, purely for flavor -- `Sources/Admin/SkinControl.pm:1103` **[CODE]** contains the comment: ```perl # A Mecham Sanity-Saver (TM) ``` Someone else's joke about a departed colleague's coding idiom, preserved in a shipping commercial product. #### 2.4 The botched rebranding pass `Sources/Admin/Index.pm:1-17` **[CODE]**: ```perl package Admin::Index; use strict; ################################################################################# # Ikonboard v3 by Jarvis Entertainment Group, Inc. # # No part of this script can be used outside Ikonboard without prior consent. # # Mor2001-e information available from # (c)2002 Jarvis Entertainment Group, Inc. # # http://www.ikonboard.com | http://www.jarvisgroup.net # # Please read the license for more information # # # ################################################################################# ``` Line 8 should read "More information available from". It reads `Mor2001-e`. The literal string `2001-` has been inserted between the `r` and the `e` of the word "More." This is a mechanical edit, not a typo: no human types `Mor2001-e`. An automated find-and-replace inserted a year fragment into an English word in the middle of a comment block, and the result shipped. **[INFERENCE]** The most economical explanation is a copyright-year update pass (of the kind that produced the `2001` -> `2001-2002` change visible in `UserCP/Menu.pm`) whose search pattern was under-anchored and matched inside prose. I cannot recover the exact rule from a single surviving instance, and I will not guess at one. What the artifact establishes on its own is sufficient and important: - The Jarvis-era headers were applied and maintained by **automated bulk edits**, not by hand. - Those edits were **not reviewed**. A corrupted word sat in the header of the admin control panel's entry-point module through at least one release and one repackaging. - Combined with the 88 header-less files and the two Ikonboard.com survivals, this places the reliability of header-based provenance in this tree fairly low. Headers in Ikonboard 3.1.1 are evidence of *which passes ran over a file*, not of who wrote it. #### 2.5 Two header dialects Splitting the 179 Perl files by comment prefix **[CODE]**: | Style | Count | Examples | |-------|------:|----------| | `#\|` pipe-rule box, `#+---+` delimiters | 35 | `ikonboard.cgi`, `installer.cgi`, `Sources/Lib/FUNC.pm`, `Sources/Lib/ADMIN.pm`, `Sources/Sessions.pm`, `Sources/UserCP/Menu.pm`, `Sources/Memberlist.pm`, `Sources/Help.pm`, all seven `install_modules/*.pl`, nineteen `Languages/en/*Words.pm` | | `#` plain box, `####...####` delimiters, with `ib-license@jarvisgroup.net` | 77 | all of `Sources/Admin/*` except the survivals, `Sources/Calendar.pm`, `Sources/NotePad.pm`, `Sources/iPoll.pm`, `Sources/Massmsend.pm`, `Search/api.pm` | | none | 67 | entire `Skin/Default/`, entire `iDatabase/`, all vendored third-party | The pipe style is the older dialect. Both Ikonboard.com survivals use it; Mecham's installer byline uses it; the core libraries `Lib/FUNC.pm` and `Lib/ADMIN.pm` use it. The plain style is the later one and is the only style that carries the license contact address `ib-license@jarvisgroup.net`. The transition is not clean -- `install_modules/functions.pm:3-14` is a hybrid, pipe-rule delimiters with the Jarvis license line inside **[CODE]**: ```perl #+-----------------------------------------------------------------+ #| Ikonboard v3 by Jarvis Entertainment Group, Inc. #| #| No parts of this script can be used outside Ikonboard without prior consent. #| #| More information available from #| (c)2001 Jarvis Entertainment Group, Inc. #| #| http://www.ikonboard.com #| #| Please Read the license for more information #+-----------------------------------------------------------------+ ``` **[INFERENCE]** The dialect boundary correlates with, but does not cleanly separate, pre- and post-handover authorship. Ten files carry the hybrid form, which is what you get when a bulk pass rewrites the *content* of a header without normalizing its *frame*. Treat the dialects as an ordering signal -- pipe is earlier -- and not as an attribution. #### 2.6 The corporate address, and one anomaly `license.html` states its manufacturer **[DOC]**: ``` Jarvis Entertainment Group, Inc. Ikonboard Solutions 14435 FM 2920 Tomball, TX 77375 Phone: 434-352-9311 World Wide Web: http://www.ikonboard.com E-mail: support@ikonboard.com ``` 2.1.9's license states **[DOC]** (`ib219/license.html:172`): ``` Ikonboard.com, 69 Brockhurst Road, Gosport, Hants, PO12 3AR, UK World Wide Web: http://www.ikonboard.com E-mail: support@ikonboard.com ``` A residential street address in Gosport, Hampshire becomes a farm-to-market road in Tomball, Texas, and a division name -- "Ikonboard Solutions" -- appears. The governing law changes from the United Kingdom to the United States (section 2.6.3), and the nominal damages cap changes currency from `U.K. GBP 1.00` to `U.S. $1.00`. The anomaly: **the phone number is not a Texas number.** Area code 434 is central Virginia (Lynchburg / Charlottesville / Danville), created in 2001 from a split of 804. Tomball, Texas is in the Houston metro, area codes 281/713/832. The license lists a Texas mailing address with a Virginia telephone number. **[EXTERNAL]** Invision Power Services, Inc. -- the company Matt Mecham and Charles Warner founded after leaving Jarvis Entertainment Group -- is listed in business directories at Forest, Virginia, with a 434 telephone number. **[EXTERNAL]** Mecham himself, in an interview published 04/22/2004, describes the arrangement at IPS as himself in the UK and Warner in the US. **[INFERENCE]** The Virginia number on a 12/18/2001-dated Jarvis license document is consistent with Ikonboard's US-facing operation being run out of central Virginia by staff who subsequently founded IPS there, with Tomball serving as the parent company's address of record. I could **not confirm** this. I found no document tying the specific number 434-352-9311 to any named person, and no source stating where JEG's Ikonboard division physically sat. The area-code mismatch is a fact; the explanation is a guess, and it should be labeled as one wherever it is repeated. --- ### 2.3 Matt Mecham's departure #### 3.1 The primary sources in the box Ikonboard 3.1.1 ships two documents signed by Matt Mecham. Both are datable, both are in the distribution, and together they bracket his tenure under Jarvis ownership from the inside. **`Tools\HELP\read_me.txt`** -- the instructions for `perl_test.cgi`, the environment checker a new installer runs first. Complete text **[DOC]**: ``` Ikonboard Perl Tester --------------------- This script will be able to tell you if you have perl running, and the sufficient modules to run it. It will also try to determine the path name, and the sendmail path for you. Instructions ------------ Simply upload the script into the directory you wish to install Ikonboard into. Check with your webhost for the correct CHMOD value for executing perl scripts (usually 755). Chmod the script to that value, and call it through your web browser (http://www.yourdomain.com/perl_test.cgi) If the script outputs as text, you do not have perl installed in that directory. That's it! --Matt Mecham () 29/5/01 ``` Signed and dated `29/5/01` -- day-first, British convention, i.e. **05/29/2001**. The file's mtime is `2001-05-29 23:13` and `perl_test.cgi` alongside it is `2001-05-29 23:10` **[DOC]**. The document's internal date and the filesystem agree to within three minutes of each other on the same day. This is as clean a primary source as an archive gets. **`Upgrading\iB2-iB3_Upgrading\Read_Me.txt`** -- the iB2-to-iB3 migration guide, 139 lines, mtime `2001-08-30 04:31` **[DOC]**. It ends: ``` << END OF FILE>> $ Matt - 30 August 2001, 2:16am ``` The signature line reads `2:16am`; the file's UTC mtime is `04:31`. **[INFERENCE]** A two-hour-fifteen-minute gap between "I finished writing this" and "this file was last written" is exactly what you expect from British Summer Time (UTC+1) plus a save, or from a copy step during packaging. It is not a discrepancy; it is a timezone. The document's content matters as much as its date. It is written in the first person plural of someone speaking *for the vendor*, about a product that is *not finished* **[DOC]**: ``` Please keep in mind that Ikonboard is still in BETA, this means that there are probably still bugs, and some features are not fully working. We are currently using an Ikonboard 3 BETA as our support board, so we know that there are no major issues. ``` and about vendor support channels he speaks for **[DOC]**: ``` > GETTING SUPPORT ................................................................. The first stop for Ikonboard related help is our support board (http://forums.ikonboard.com). We are setting up a help channel on our IRC server. You may want to visit 'irc.ikonboard.com' on port number '6667' and join channel #support. Alternatively, you can email us on "support@ikonboard.com" - please note that it may take up top 48 hours to get a response during busy periods. ``` ("up top 48 hours" -- sic.) The IRC server, port, and channel here are the same ones still printed in the header of `ikonboard.cgi` fifteen months later. Mecham was documenting Jarvis-era Ikonboard's support infrastructure on 08/30/2001. **What this bounds.** On 08/30/2001 Matt Mecham was still writing official Ikonboard 3 documentation, speaking as the vendor, describing iB3 as a beta in production use on the company's own support board. Whatever else is true about his departure, it had not happened yet. The distribution also proves it in a second way: this Read_Me and its three redirect scripts (`Tool_Box/ikonboard.cgi`, `forums.cgi`, `topic.cgi`, all `2001-08-30 04:31`) were still shipping *inside the 3.1.1 box* in November 2002, more than a year later, unrevised -- the vendor never rewrote the migration guide after he left. #### 3.2 The external record **[EXTERNAL]** The sequence, from encyclopedia entries and one first-person interview: - Ikonboard originated as freeware by Matt Mecham, first release 0.9 beta in **September 1999**, written in Perl with flat-file storage, operating from ikondiscussion.com. - A server crash in **March 2001** prompted a move to ikonboard.com. - In **late April 2001**, "Ikonboard officially joined the Jarvis Network." Mecham sold the software to Jarvis Entertainment Group. The consideration is reported as **50,000 shares of common stock**, which he later said he was unable to liquidate. **[UNVERIFIED]** I could not find a primary document -- SEC filing, press release, or statement by Mecham -- for the 50,000-share figure. It appears in encyclopedia entries without a citation I could follow. Treat the number as folklore until someone produces the paper. - Ikonboard 3.0 was released under Jarvis. A JEG corporate news item headlined **"JEG Release iB3; The Leader in Community Building Software"** is dated **2001-11-05** in the sidebar of every ikonboard.com page captured in 2002 **[ARCHIVE]** (`web.archive.org` capture of `www.ikonboard.com`, 10/17/2002 and 12/10/2002). - **February 2002**: Invision Power Services created by Charles Warner and Matt Mecham, both formerly of Jarvis Entertainment Group. The last point has a first-person source. In an interview published **04/22/2004** **[EXTERNAL]**, Mecham states that he and Warner both worked for the company that acquired Ikonboard, were dissatisfied with its direction, left, and that "Invision Power Services was created in February 2002." He describes starting IBForums -- later renamed Invision Power Board -- because "I felt I still had some more to add to the bulletin board market and as we had a lot of experience in that area it seemed the sensible thing to do," and notes that IPS funded itself on hosting packages while IPB was under development. **[EXTERNAL]** Invision Power Board was PHP and MySQL from the start. Early 1.x releases were free downloads under a proprietary license; free non-commercial releases ended in 2004. IPB "quickly gathered a community of former Ikonboard users." #### 3.3 The bracket Combining the shipped documents with the external record: ``` 1999-09 Ikonboard 0.9 beta. Mecham, Perl, flat files. [EXTERNAL] 2001-03 ikondiscussion.com server crash; move to ikonboard.com. [EXTERNAL] 2001-04 Ikonboard joins the Jarvis Network. Sale to JEG. [EXTERNAL] 2001-05 iDatabase v1.0 written. Author: Matthew Mecham. [CODE] CSV.pm:6 2001-05-29 perl_test read_me signed "--Matt Mecham ... 29/5/01" [DOC] <-- HERE, still vendor 2001-08-30 iB2->iB3 migration guide signed "$ Matt - 30 August 2001, 2:16am" [DOC] <-- HERE, still vendor 2001-11-05 JEG announces iB3 release. [ARCHIVE] 2002-02 Invision Power Services founded, Warner + Mecham. [EXTERNAL] <-- gone by HERE 2002-06-12 JEG releases Ikonboard 3.1. [ARCHIVE] ``` **The departure falls in the window 08/30/2001 to 02/2002**, a span of about five months, bounded on the left by a document in the box and on the right by Mecham's own account of when IPS was created. I could not narrow it further. No source I found gives a resignation date, a last-commit date, or a public announcement. **[INFERENCE]** The `iDatabase` timestamps offer a soft additional bound. The layer carries a May 2001 authorship stamp and every driver still reports `$VERSION = 1.0` fourteen months later, in a release that added a full per-backend *search* API on top of it (`Sources/Search/API/api_DBM.pm`, `api_mySQL.pm`, `api_pgSQL.pm`, `api_Oracle.pm`). The team built above Mecham's database layer without revising it. That is consistent with its author no longer being available to revise it, and inconsistent with him being around and active through 3.1. It is not proof -- plenty of stable code goes untouched -- but it is the shape you would predict. The counter-observation, for honesty: `Sources/Sessions.pm` and `Sources/Lib/FUNC.pm`, both pipe-dialect and both plausibly Mecham-era in origin, *were* modified on 07/13/2002. So the older strata were not frozen. Someone was editing them; that someone just was not updating their headers. #### 3.4 Invision Power Board as the successor **[EXTERNAL]** Invision Power Board is the direct descendant of Mecham's work in the sense that matters -- same author, same problem domain, same user base, same market position -- but it is not a fork. IPB was written in PHP against MySQL; Ikonboard 3 is Perl. The continuity is in the person and the community, not the source tree. The chronology puts IPB and Ikonboard 3.1 in the market simultaneously in 2002, with the Ikonboard user base as the contested ground and with IPB on the technology stack that would win (section 2.9.3). **[EXTERNAL]** IPB's later releases: 3.0 on 06/23/2009, 4.0.0 officially 04/09/2015; the product line continues as Invision Community. The Perl Ikonboard line ended in 2010 (section 2.9.1). --- ### 2.4 The development team of iB3 #### 4.1 What the source names Ikonboard 3.1.1 is a *shipped commercial product* that carries inline development commentary of a kind normally stripped before release. Harvesting every credit-shaped comment from the tree **[CODE]**: | File:line | Comment | |-----------|---------| | `ikonboard.cgi:342` | `# ( ADDED HERE BY KEVaholic00 FOR BUG FIX #168, COMMENTED OUT BELOW )` | | `ikonboard.cgi:462` | `# Added by KEVaholic00: member notepads` | | `ikonboard.cgi:464` | `# Added by Camil: Newest post` | | `Database/config/member_profiles.cfg:51` | `# added by kevaholic00` | | `Languages/en/MessengerWords.pm:23` | `# added by camil` | | `Languages/en/MessengerWords.pm:145` | `# Added by Infection` | | `Languages/en/ModerateWords.pm:13` | `## Added by Camil for event calendar` | | `Languages/en/ModerateWords.pm:21` | `## Added by LrdChaos 5/7/02 for watched topic` | | `Languages/en/ProfileWords.pm:56` | `# Added by Infection` | | `Languages/en/TopicWords.pm:22` | `# added by camil for topic streamlining` | | `Languages/en/TopicWords.pm:29` | `## added by LrdChaos 5/7/02 for topic watch` | | `Languages/en/TopicWords.pm:32` | `## end added by LrdChaos 5/7/02` | | `Languages/en/UserCPWords.pm:6` | `# added by kevaholic00 for "post font color in userCP"` | | `Languages/en/UserCPWords.pm:10` | `# added by kevaholic00` | | `Skin/Default/Menu.cfg:195` | `# added by Infection` | | `Skin/Default/MenuView.cfg:369,381` | `` ... `` | | `Skin/Default/MenuView.pm:8,485,497` | `# added by kevaholic00` / HTML comment pair | | `Skin/Default/ModCPView.cfg:356` | `` | | `Skin/Default/ModCPView.pm:4,21,1077` | `# added by kevaholic00` x2, HTML comment | | `Sources/Admin/Category.pm:535,538` | `# Added by LrdChaos 4/8/02` ... `# End added by LrdChaos` | | `Sources/Admin/Category.pm:695,698` | `# Added by LrdChaos 4/8/02` ... `# End added by LrdChaos` | | `Sources/Admin/Convert_ib.pm:1210` | `# Thanks to 'joshdw1' for his assistance, time and server to fix this up.` | | `Sources/Admin/Menuadmin.pm:203-204` | `` `` | | `Sources/Admin/ModControl.pm:447,800` | `## End added by LrdChaos 5/7/02` | | `Sources/iDatabase/Driver/Base.pm:25` | `# Routine written by Nurlan ("infection")` | | `Sources/iTextparser.pm:78,237,372` | `# added by kevaholic00` x3 | | `Sources/iTextparser2.pm:78,237,372` | `# added by kevaholic00` x3 | | `Sources/Lib/FUNC.pm:214` | `# This routine written by "Infection"` | | `Sources/Lib/FUNC.pm:598` | `#AM/PM SUFFIX bug fix by Freakboy` | | `Sources/ModCP.pm:1955` | `# added by kevaholic00` | | `Sources/Register.pm:218` | `# added by Infection` | Handles recovered from the source: **KEVaholic00** (by far the most prolific), **Camil**, **LrdChaos**, **Infection**, **Freakboy**, **joshdw1**. Two of the credit lines carry dates in American format -- `LrdChaos 4/8/02` and `LrdChaos 5/7/02`, i.e. 04/08/2002 and 05/07/2002 **[CODE]**. These sit two months and five weeks respectively before the 06/12/2002 release of 3.1, and are the only hand-written dates anywhere in the source. #### 4.2 Real names, recovered from module headers Three contributors are named in full inside the shipped code **[CODE]**: `Sources/Admin/SQLclient.pm:18` -- the in-browser SQL client: ```perl # Script Author: Nurlan Mukhanov (Infection) ``` `Sources/Calendar.pm:8` -- the event calendar: ```perl # Script author: Nurlan Mukhanov (Infection) Modif. by Camil ``` `Sources/iDatabase/Driver/Oracle.pm:7` -- the Oracle backend: ```perl # Driver Author: Andrey Prokopenko ``` `Sources/Upgrade.pm:15` -- the upgrade module: ```perl # Script Author: Phil Gengler (LrdChaos) ``` `Sources/Warn.pm:183-186` -- POD block, member warning system: ``` =AUTHOR Phil Gengler ``` So: | Handle | Real name | Contact in source | Contributions visible in tree | |--------|-----------|-------------------|-------------------------------| | Infection | **Nurlan Mukhanov** | `webmaster@tourist.kz` | `Admin/SQLclient.pm` (whole module, 29 KB), `Calendar.pm` (original author), `Lib/FUNC.pm:214` `htmlcut`, `iDatabase/Driver/Base.pm:25` `make_hash_ref`, `Register.pm:218`, language and skin strings | | -- | **Andrey Prokopenko** | `faceless@ortv.ru` | `iDatabase/Driver/Oracle.pm` (whole driver, 33 KB) | | LrdChaos | **Phil Gengler** | `lrdchaos@codeallday.com` | `Upgrade.pm`, `Warn.pm`, watched/subscribed topics (`Admin/ModControl.pm`, `Languages/en/TopicWords.pm`, `Languages/en/ModerateWords.pm`), `Admin/Category.pm` | | KEVaholic00 | not stated | not stated | member notepads (`NotePad.pm` + dispatcher wiring), avatar upload, post font color, the admin webring, `iTextparser` changes, `ModCP.pm`, skin and language additions, bug fix #168 | | Camil | not stated | not stated | "Newest post" (`Newest.pm` + dispatcher wiring), event-calendar modifications, topic streamlining, messenger strings | | Freakboy | not stated | not stated | `Lib/FUNC.pm:598` AM/PM suffix bug fix | | joshdw1 | not stated | not stated | assistance, time, and a server, for `Admin/Convert_ib.pm` avatar handling | The `.ru` and `.kz` addresses are worth pausing on. `tourist.kz` is a Kazakhstan domain; `ortv.ru` is Russian. Ikonboard 3's SQL admin client and its Oracle driver -- two of the more specialized pieces of infrastructure in the product -- were written by contributors in the former Soviet Union, contributing to a Texas company's product, in 2002, and credited by name and email address in the shipping source. **[INFERENCE]** This is a volunteer open-community pattern, not an employment pattern. No company ships its employees' personal webmaster addresses in product headers. #### 4.3 The roster, from the vendor's own site Two archived captures of ikonboard.com's staff page confirm the handles and date them. **Capture 06/05/2002** -- one week before the 3.1 release **[ARCHIVE]** (`web.archive.org/web/20020605064230/http://www.ikonboard.com/?team`). The page's own framing line: > Here you will find our current Ikonboard Network **Volunteer** Staff, If you would > like to be a part of our Ikonboard Network Team please E-Mail us [HERE]. The roster as captured: ``` Administrators Gladiator Gladiator@Ikonboard.com Fender ctraweek@ikonboard.com Quasi itsmequasi@hotmail.com Katiou Ace webmaster@animeglobe.com Team Leaders Brush Quattlebomb@hotmail.com Redbaron andrews@ikonboard.com Malkavian Malkavian.isupport@ikonboard.com Sly sly.isupport@ikonboard.com Benjamin Liger benjamin@stripeymaney.com Wedge wedge@dualboot.net LrdChaos lrdchaos.idev@ikonboard.com Development Team Camil ccollard@enter-net.com KEVaholic00 kevaholic00@yahoo.com JayLittle N/A Jevon jevon.idev@gta3.com Support Team Ayin, Breadfan, Darkone, David-iB, dnekm, DragoonKain, h1995vn, LSGN, Semper Lizardo, Victor von Steiner, Z3RO 0, jimrobnor, Physbird iBH Leader Leosite iBH Staff Eergassie, jamble, omega13a, wbagent, BGF, Britishguy87 ``` ("Katiou Ace" is a typo for Kaitou Ace, corrected in the later capture.) **Capture 10/17/2002** -- four months later, five weeks before the 3.1.1 repackaging **[ARCHIVE]** (`web.archive.org/web/20021017024616/http://www.ikonboard.com/?team`): ``` Administrators: Anarckie, Fender, Kaitou Ace, Quasi Head of Support: Cekkent Team Leaders: Malkav, Redbaron, Sly, Wedge, Victor von Steiner, Predator668 iB3 Development Team: Jevon, KEVaholic00 , Porter , Pysbird, simonpersson, Sanjeet Ganjam iB3 Support Team: Ayin, Breadfan, Darkone, David-iB, h1995vn, Hudzon, jimrobnor, Mark64uk, SKJain, Digikoon iB2 Support & Dev: BGF iBH Leader: Eergassie, Leosite iBH Staff: AlanA, Bram, omega13a, Sevigon, wbagent iB Skinner: Akuma, neliconcept, Nimrod44 MYiSupport Team: Chris Jenkins ``` Cross-referencing against the source-code credits: | Handle in source | On 06/05/2002 roster | On 10/17/2002 roster | |------------------|----------------------|----------------------| | KEVaholic00 | Development Team | iB3 Development Team | | Camil | Development Team | **absent** | | LrdChaos | Team Leaders (`lrdchaos.idev@`) | **absent** | | Infection (N. Mukhanov) | absent | absent | | Freakboy | absent | absent | | joshdw1 | absent | absent | **[INFERENCE]** Of the six people credited by name inside Ikonboard 3.1.1, exactly one was on the vendor's staff page when 3.1.1 was repackaged. Camil and LrdChaos were listed in June 2002 and gone by October 2002; Infection, who wrote two whole modules and two core routines, never appears on either roster. The credits in the source are therefore not a staff list. They are a *sedimentary record of contributions accepted over time*, from people who were sometimes on the team and sometimes not. Note also `porter@jarvisgroup.com` on the October roster -- the only development-team member with a corporate address. **[INFERENCE]** One paid developer, on a team of six, in October 2002. Wikipedia's account of this period names "Sly, Camil, and Quasi" as the community developers who continued after Mecham left **[EXTERNAL]**. All three are on the June 2002 roster; Camil is the only one of the three credited in the source. The Wikipedia framing is broadly corroborated by the captures but is imprecise about roles -- Sly and Quasi were a Team Leader and an Administrator respectively, not the Development Team. #### 4.4 The public bug tracker `ikonboard.cgi:342` **[CODE]**: ```perl # ( ADDED HERE BY KEVaholic00 FOR BUG FIX #168, COMMENTED OUT BELOW ) # Lets add on the skin name for ease of use. my $images_url = $iB::INFO->{'IMAGES_URL'}; $iB::INFO->{'IMAGES_URL'} .= '/' . $iB::SKIN->{'FULL_DIR'}; # ( END ADDITION ) ``` A numbered bug reference in the shipped dispatcher. `out_provenance.txt` section 2.4 finds exactly one such reference in the tree, so this is not a convention -- it is one person's habit, applied once. The tracker it refers to is identifiable. The 06/03/2001 capture of ikonboard.com's navigation includes a **Bug Tracker** item linking to `http://bugs.ikonboard.com/` and `http://bugs.ikonboard.com/index.php` **[ARCHIVE]** (`web.archive.org/web/20010603051912/http://www.ikonboard.com/team/`). The same navigation offers Support, Forums, Features, Requirements, Member Center, Documentation, Bug Tracker, Extra's, BBS Convertors, Links, and Chat. **[UNVERIFIED]** I could not retrieve bug #168 itself. `bugs.ikonboard.com` was a database-backed PHP application; the Archive's captures of it, if any, would not contain individual issue pages, and I found none. What #168 *was* is unknown. What the reference proves is that in 2002 there was a public, numbered, externally addressable bug tracker whose issue IDs a volunteer developer expected a reader of the source to be able to look up. **Why this matters.** A commercial software product that ships with `ADDED HERE BY FOR BUG FIX #` in its main executable is telling you its development model outright: 1. **The bug list was public.** Otherwise the number is meaningless to the reader. 2. **Attribution was social, not legal.** Contributors signed their patches with the handles they used on the support forums, in a codebase whose license forbids anyone claiming ownership of amended files (section 2.6.2). 3. **Patches went in as patches.** The comment is a diff marker: `ADDED HERE ... ( END ADDITION )`. Nobody integrated the change and cleaned up after it; the seam is preserved. The same bracketing appears throughout -- `Languages/en/TopicWords.pm:29,32`, `Sources/Admin/Category.pm:535,538`, `Skin/Default/MenuView.pm:485,497`. 4. **There was no code review pass before release.** The seams shipped. So did `Mor2001-e` (section 2.2.4). #### 4.5 The hack-writing SDK, and what it produced The distribution ships a development kit. `Tools\writing_hacks\module_template.pm`, mtime `2001-10-30 04:03`, 197 lines, opens **[DOC]**: ```perl ################################################ # # This is a module template. # This is designed to give you a head start on creating # new modules for iB3 # ################################################ ``` It is a genuine tutorial -- it walks a novice through package declaration, the `BEGIN` block that pulls in `Lib/FUNC.pm`, constructing the `FUNC::STD` / `FUNC::Member` / `FUNC::Output` objects, loading the skin file, writing a subroutine, and wiring the module into the dispatcher via a `%Mode` hash. It explains Perl to the reader in a friendly register **[DOC]**: ```perl # Although $time_elements looks like a standard (scalar) variable - it is actually # "tied" to the subroutines in the module. It works along the same lines as the "new" constuctor # we saw. ``` ```perl # If we wanted to print the year, all we'd need to do is.. # print $time_elements->year; # This accesses a subroutine called "year" in Time::localtime # If we wanted to print the converted hour, we'd need to print... # print $time_elements->hour; # Seeing a pattern? :D ``` It ends with a stub dispatcher and a POD skeleton **[DOC]**: ```perl sub FatalError { die "I'm working on it!" } 1; __END__ =pod =NAME Module name here =DESCRIPTION What the module does here =AUTHOR Your Name Here =cut ``` **Two modules in the shipping product were written from this template and shipped with the template's boilerplate still in them.** `Sources/Warn.pm` -- the member warning system, a headline 3.x feature -- is 191 lines and ends **[CODE]**: ```perl sub FatalError { die "I'm working on it!" } 1; __END__ =pod =NAME Module name here =DESCRIPTION What the module does here =AUTHOR Phil Gengler =cut ``` Only the `=AUTHOR` field was filled in. `=NAME` still says "Module name here." `=DESCRIPTION` still says "What the module does here." The tutorial's placeholder error handler, `die "I'm working on it!"`, is live code in the shipped module at `Sources/Warn.pm:167`. And `Warn.pm` carries **no copyright header at all** -- the template did not have one, so the module does not have one. `Sources/Legends.pm:174` has the same fossil under a different name **[CODE]**: ```perl sub LegendError { die "I'm working on it!" } ``` **[INFERENCE]** This is the clearest single artifact of the iB3 development model. The vendor published a "write your own hacks" SDK; a community member wrote a feature with it; the vendor promoted that feature into the product; and nobody normalized it on the way in. The boundary between "third-party hack" and "shipped product" was not a boundary at all -- it was a copy operation. The pipeline that turned a forum post into a product feature had no editorial step. The corollary matters for anyone reading this code: **module quality in Ikonboard 3.1.1 is not uniform, and the non-uniformity is structural.** `Sources/iDatabase/` is careful, documented, versioned, object-oriented code by one author. `Sources/Warn.pm` is a tutorial exercise with the tutorial comments removed and the tutorial placeholders left in. Both ship. #### 4.6 Volunteer work in the product's own furniture Two further places show community contributions embedded not in a peripheral module but in the product's core furniture. **The database schema.** `Database/config/member_profiles.cfg:39-53` **[CODE]** -- this file *defines the member table*: ```perl "GENDER" => [38, 'num' , 1 ], "MEMBER_NAME_R" => [39 , 'string', 40, ], # added by kevaholic00 "POST_FONT_COLOR" => [40, 'string', 15 ], # end add ); ``` A volunteer's column, with his attribution comment, in the shipped schema definition that every installation's member table is created from. **The admin menu.** `Sources/Admin/Menuadmin.pm:202-206` **[CODE]**: ```html

> Manage Web Ring ``` The webring is a real feature with a real module -- `Sources/Admin/WebRing.pm`, 3,063 bytes, mtime `2002-06-24 05:20`, carrying a full Jarvis copyright header **[CODE]**. A volunteer's contribution was given a menu item, a module, and a corporate copyright notice, and his attribution comment survived in the menu markup that renders it. **Language pack authorship as a UI concept.** `Sources/Admin/LangControl.pm:206-207` and `Sources/Admin/SkinHandler.pm:154-155` **[CODE]** both build the same pair of required form fields: ```perl $html .= $SKIN->td_input ( TEXT => 'Authors Name', NAME => 'AUTHOR_NAME', VALUE=> '', REQ => 1); $html .= $SKIN->td_input ( TEXT => 'Authors Email Address', NAME => 'AUTHOR_EMAIL', VALUE=> '', REQ => 1); ``` Language packs and skins are first-class contributable artifacts in Ikonboard 3, with mandatory author name and email metadata built into the admin UI. The community contribution model is not incidental to the product; it is a designed-in feature of it. #### 4.7 What the team thought of the code The tree preserves unusual candor. `Sources/iPoll.pm:20-30` **[CODE]**: ```perl # For the future # # Make the posting / polling more modular. This whole module is held together with # duct tape. It works fine, it's just ugly and carries bad coding principles. # # Something like: # Postings/ # -- Global.pm #Hold global posting elements such as the HTML compiler, etc # -- NewPost.pm # New post routines # -- ReplyPost.pm #ummm. ``` `Sources/iPoll.pm:138` **[CODE]**: ```perl # This is horrible and I hate it. ``` `Sources/iPoll.pm:158` **[CODE]**: ```perl # Cut off a piece of duct tape... ``` `Sources/Admin/BoardTemplates.pm:154-155` **[CODE]** -- on the license-enforcement check described in section 2.6.4: ```perl #XXX No doubt someone will remove the <% IKONBOARD %> tag... #XXX Then post on the support board that their board has gone 'missing'. ``` `out_provenance.txt` section 2.4 counts 84 comments of this general "unfinished work" character. The `XXX` prefix, used throughout `Boards.pm`, `Moderate.pm`, `Online.pm`, `Admin/ForumControl.pm`, and others, is used here as a plain step-marker rather than the conventional "this is broken" flag -- `XXX Grab all the forums in this main category`, `XXX DONE!` -- which is itself an idiom mismatch between contributors. #### 4.8 The tab damage A physical artifact of multi-contributor editing runs through the tree. Many files contain literal tab characters *inside string content and inside comment prose*, where no tab belongs: ``` #| (c)2001 Jarvis Entertainment Group, Inc. <- Sources/Help.pm # (c)2001-2002 Jarvis Entertainment Group, Inc. <- Sources/Admin/LangControl.pm #| Ikonboard [ v3.1 ] <- Sources/UserCP/Menu.pm # Script author: Nurlan Mukhanov (Infection) Modif. by Camil <- Sources/Calendar.pm XXX Skip it if the category has already been printed <- Sources/Boards.pm ``` `out_provenance.txt` reproduces several of these faithfully, which is why its list of copyright variants has ten entries for six distinct copyright strings. **[INFERENCE]** Runs of spaces have been converted to tabs by a tool or an editor setting, indiscriminately, including inside comments and inside quoted HTML. It is the signature of a file passing through an editor configured differently from the one that wrote it -- precisely what happens when a patch travels from a contributor's machine through a forum post or an email to a maintainer's machine. The damage is cosmetic in Perl (whitespace outside strings is insignificant) but it is a reliable fingerprint of the distributed authorship model, and it appears in files across every dialect and every stratum. --- ### 2.5 Licensing `license.html` in the 3.1.1 distribution has mtime `2001-12-18` -- six months older than the code it ships with, and roughly six weeks after the 11/05/2001 iB3 announcement. It is the JEG-era license, written once and reused. #### 5.1 What 2.1.9's license said `ib219/license.html` **[DOC]**, in summary and in its own words: - **Price**: `Personal Use - GBP 0.00 (Free)` / `Commercial Use - GBP 0.00 (Free)`. - **Copyright removal**: `Cost - GBP 200.00`, contact `matt@ikonboard.com`. "This will enable you to remove the copyright information links and link to ikonboard.com present in all the .cgi generated files." - **Condition of use**: "you must ensure that the Ikonboard copyright notice, and links back to ikonboard.com are left intact and are visable." - **Modification**: "You may edit the .cgi files and images as long as you do not break any terms of this license." One sentence. No enumerated conditions. - **Distribution**: free redistribution permitted, with (A) no payment beyond distribution costs, (B) "You do not modify it in any way," (C) complete original zip archive including the license. - **Ownership**: non-exclusive license; all rights remain with Ikonboard.com; may not modify or translate without written permission. - **Damages cap**: "the greater of U.K. GBP 1.00." - **Governing law**: United Kingdom. - **Manufacturer**: Ikonboard.com, 69 Brockhurst Road, Gosport, Hants, PO12 3AR, UK. Total document: 179 lines including HTML. #### 5.2 What 3.1.1's license says `ib311/license.html` **[DOC]**. The structure is inherited -- the section headings and much of the boilerplate are the same document -- but every commercially meaningful clause has been tightened, and four entirely new provisions appear. **Pricing** (`license.html:13-15,41`): ``` Personal Use - $0.00 (Free) Commercial Use - $0.00 (Free) This enables you to use Ikonboard in accordance with this license. ``` ``` License Ikonboard without the copyright information Cost - $250.00 This will enable you to remove the copyright information links and links to Ikonboard sites present in the output of all the program files. You must still retain all the copyright information intact in all the script headers. Purchasing a license also entitles you to 30 days of priority technical support. Please email ib-license@jarvisgroup.net for more information or for questions about this license agreement. ``` The software remained **free of charge for both personal and commercial use.** The only money in the license is the branding-removal fee, which rose from GBP 200 to $250 and acquired a support entitlement. Three substantive changes in this clause alone: 1. `link to ikonboard.com` (singular, one destination) became `links to Ikonboard sites` (plural, unbounded set) -- see the Copyright Notice clause below, which makes this explicit. 2. `present in all the .cgi generated files` became `present in the output of all the program files` -- 3.x has no user-visible `.cgi` files besides the dispatcher, so this is partly a technical correction, but it also broadens the scope from "generated files" to "output." 3. A new carve-out: even after paying $250, **"You must still retain all the copyright information intact in all the script headers."** In 2.1.9 the fee removed the links; in 3.1.1 the fee removes only the *visible page* branding, and the source headers stay regardless. Purchasing does not buy you a de-branded source tree. **Copyright Notice** (`license.html:69-91`): ``` Ikonboard by Jarvis Entertainment Group, Inc. Copyright (c) 2001 Jarvis Entertainment Group, Inc. ("JEG"). All Rights Reserved. Ikonboard ("Software") is licensed, not sold to you by JEG for use only under the terms of this license agreement. JEG reserves any rights not expressly granted to you. To keep this license valid, you must ensure that the Ikonboard copyright notice, and link back to ikonboard.com or ikondiscussion.com or any other domains existing in the software at time of download are left intact and are visible. You must also keep all the copyright information intact in all the script headers, and in the scripts themselves. ``` Note the clause `or any other domains existing in the software at time of download`. This is a forward-looking hook: whatever hostnames JEG chose to embed in a future build would automatically become protected links. In the shipped 3.1.1 that set includes eight distinct network hosts in the admin panel alone (section 2.7). **[INFERENCE]** The license was drafted to make the Network Links block legally load-bearing. Note also `ikondiscussion.com` -- the pre-March-2001 home of the project **[EXTERNAL]** -- still named in a December 2001 license by a Texas company. Another survival of the Mecham era in a document that ostensibly replaced it. **The open-source disclaimer** (`license.html:97`), entirely new: ``` Although Ikonboard is not open source software, you are granted permissions to edit the program files and images as long as you do not break any terms of this license. Please review the Ikonboard Use section of this license for specific details on modification permissions. ``` 2.1.9 never used the phrase. By late 2001 "open source" had enough market meaning that a free-of-charge proprietary product needed to explicitly disclaim it -- and the disclaimer sits in the *Copyright Notice* section, ahead of any grant of rights. **Ikonboard Use** (`license.html:205-229`) -- where 2.1.9 had one sentence, 3.1.1 has a six-clause enumeration: ``` You may use the Ikonboard on one or more computers, or on a network, for an unlimited time, unless you break any agreements you make in accordance with this license. JEG reserves the right to terminate your license at any time for any or no reason. You are granted permission to make modifications to the Software as long as: * You do not modify this license in any way * You do not claim ownership of any ammended files * You do not remove any copyright notices including but not limited to; the copyright in the HTML output and the copyright in the source files * You do not distribute the ammended files without written permission from JEG * You do not change the 'ikonboard' name in any of the source files or generated URLs * You accept that modifying the code does not grant you any copyrights or intellectual rights other than your own statutory rights ``` ("ammended" -- sic, twice.) Read against section 2.4 of this chapter, these six bullets describe the iB3 development community's legal position exactly: - *You do not claim ownership of any ammended files* -- a volunteer who writes `Sources/Warn.pm` does not own `Sources/Warn.pm`. - *You accept that modifying the code does not grant you any copyrights* -- restated, in case the first pass was ambiguous. - *You do not distribute the ammended files without written permission from JEG* -- the ibhackers.com hack database (section 2.7.2) operated at the vendor's pleasure. Every "hack" distributed there was, strictly read, a licensed-file derivative requiring written permission. - *You do not change the 'ikonboard' name in any of the source files or generated URLs* -- no rebranding, no forks-by-rename. Also new: **"JEG reserves the right to terminate your license at any time for any or no reason."** 2.1.9 said "Ikonboard.com reserve the right to terminate your license at any time." The addition of "for any or no reason" is a deliberate strengthening. **Distribution** (`license.html:147-185`) -- 2.1.9's clause (B) was `You do not modify it in any way`. 3.1.1's is: ``` B) You do not distribute a version of the Software modified in any way. C) You distribute it with its complete, original, unmodified archive, including this unmodified license file. ``` The prohibition moved from *modifying* to *distributing a modified version*, which is the correct drafting -- modification is separately permitted under Ikonboard Use -- and "original zip archive" became "original, unmodified archive," matching a distribution that ships `.tar` files rather than zips. **Ownership** (`license.html:265-287`), with a new final sentence: ``` Ikonboard reserves the right to publish the users of its software at its discretion without prior notification to the user. ``` No equivalent exists in 2.1.9. **[INFERENCE]** This is a marketing-reference clause: it lets JEG name your board as a customer without asking. Read alongside the archived ikonboard.com front page's boast of "well over 1 million active boards operating across the web" **[ARCHIVE]**, its purpose is legible. **Warranty and law** (`license.html:333-345,457`): ``` In cases where the above limitations do not apply by law JEG's liability to you for actual damages for any cause whatsoever, and regardless of the form of the action, will be limited to the greater of U.S. $1.00. ``` ``` This license agreement shall be governed by the laws of the United States of America. ``` 2.1.9's damages cap was `U.K. GBP 1.00` and the governing law was the United Kingdom. Note also what was *dropped*: 2.1.9 contained a consumer-protection acknowledgement -- "Because some countries do not allow the exclusion of limitation of liability for consequential or incidental damages, the above limitations may not apply to you" -- which does not appear in 3.1.1. **Term** (`license.html:367-375`), with a new final sentence: ``` This license is only applicable to the software it was packaged with. Future or previous versions of Ikonboard may have ammended license agreements and will fall under that agreement. ``` A version-pinning clause. Nothing in this license binds JEG's future terms -- which matters, given what IPS would do to its own license in 2004 **[EXTERNAL]** and given Ikonboard's own subsequent ownership churn (section 2.9.1). #### 5.3 The comparison, tabulated | Provision | 2.1.9 (06/2001) | 3.1.1 (license dated 12/18/2001) | |-----------|-----------------|----------------------------------| | Owner | Ikonboard.com | Jarvis Entertainment Group, Inc. ("JEG") | | Address | Gosport, Hampshire, UK | Tomball, Texas, USA ("Ikonboard Solutions") | | Governing law | United Kingdom | United States of America | | Damages cap | U.K. GBP 1.00 | U.S. $1.00 | | Personal use | GBP 0.00 | $0.00 | | Commercial use | GBP 0.00 | $0.00 | | De-branding fee | GBP 200.00 | $250.00 | | De-branding contact | `matt@ikonboard.com` | `ib-license@jarvisgroup.net` | | De-branding includes support | no | yes, 30 days priority | | De-branding removes source headers | (silent) | **no, explicitly** | | Protected links | "links back to ikonboard.com" | ikonboard.com, ikondiscussion.com, **"or any other domains existing in the software at time of download"** | | "Not open source" stated | no | **yes** | | Modification permissions | one sentence | **six enumerated conditions** | | Ownership of contributed code | (silent) | **assigned away -- "You do not claim ownership of any ammended files"** | | Redistribute modified files | (implicitly barred) | **barred without written permission from JEG** | | Rename the product | (silent) | **barred, source and URLs** | | Termination at will | "at any time" | "at any time **for any or no reason**" | | Publish list of users | (silent) | **yes, without notification** | | Cross-version applicability | (silent) | **this license binds only this package** | | Consumer-protection carve-out | present | **removed** | | Document length | 179 lines | 508 lines | **Assessment.** The headline economics did not change: Ikonboard 3.1.1 was free, for everyone, forever, exactly as 2.1.9 was. What changed is the *legal architecture around* the free product. The 3.1.1 license is a professionally hardened proprietary freeware license with a paid branding-removal tier, an at-will termination right, assignment of contributor rights, a publicity right over its users, and a hook that automatically extends its link-retention obligations to whatever hostnames the vendor embeds in a build. 2.1.9's license reads as written by a developer; 3.1.1's reads as written for a company that intended to monetize an installed base. The commercial posture is real but it is a *posture*, not revenue from licensing. The money in the JEG Ikonboard business was elsewhere: hosting, a hosted-forum service, and merchandise (section 2.7). #### 5.4 How the license is enforced in code The license's central obligation -- visible copyright, intact links -- is not left to honor. It is implemented. **The copyright emitter.** `Sources/Lib/FUNC.pm:893` **[CODE]** -- one line, in the core output library, that every page passes through: ```perl my $ib_copy = qq~\n\n

~; ``` Two links to ikonboard.com and the version string, in a table with a dedicated CSS id (`id='copyright'`) so a skinner can style it but not easily excise it. `$BCopy`, built at `FUNC.pm:847`, is the *board owner's* copyright line, placed above the vendor's -- a small courtesy that also makes the block harder to justify deleting. Compare 2.1.9 **[CODE]** (`ib219/cgi-bin/ikon.lib:862`): ``` Powered by Ikonboard $versionnumber
© 2001 Ikonboard.com ``` Same idea, no CSS hook, and -- telling -- `ikon.lib:874` and `settemplate.cgi:187` still carry a `© 2000 Ikonboard.com` variant, so 2.1.9 emitted two different copyright years from three different places. 3.1.1 emits one string from one function. **The template guard.** The admin panel lets an operator write the whole page wrapper (`Sources/Admin/BoardTemplates.pm`). To stop that from being used to drop the board -- and its copyright block -- out of the page, the code requires a placeholder token `<% IKONBOARD %>` in any submitted template and refuses to save without it (`BoardTemplates.pm:154-158`) **[CODE]**: ```perl #XXX No doubt someone will remove the <% IKONBOARD %> tag... #XXX Then post on the support board that their board has gone 'missing'. #XXX Here goes nothing... unless ($template =~ m#<% IKONBOARD %>#) { $ADMIN->Error( DB=>,"", STD=>"", MSG => "You need the <% IKONBOARD %> tag!"); ``` The check exists for functional reasons -- without the token there is nowhere to inject the board -- but its effect is that the license's "left intact and are visible" requirement has a mechanical floor. **[INFERENCE]** The comments make clear the authors were thinking about operators removing it, not about license compliance; the compliance effect is a by-product. **What the $250 buys.** Since the fee removes only page-output branding, and the source headers must stay regardless, the practical deliverable of a paid license is: edit `Sources/Lib/FUNC.pm:893` and delete the `$ib_copy` table. That is the transaction. The sum of the commercial licensing product, at the code level, is one line. --- ### 2.6 The commercial network #### 6.1 The block `Sources/Admin/Menuadmin.pm:112-144` **[CODE]** -- verbatim, indentation as it appears in the file: ```perl my $nineteen = $open_menus->{'19'} ? undef : qq(#19); $html .= qq~ Network Links ~; if ($open_menus->{'19'}) { $html .= qq~
> iB AdminCP Help
> ikonboard FAQ
> iB Support Forums
> iB Member Center

> Jarvis Hosting
> iBSkins
> iBHackers
> myIkonboard
~; } ``` Menu group 19 -- the highest-numbered group in the admin sidebar, and rendered *first*, above Moderate (group 1). The vendor's network occupies the top of every administrator's control panel. Look at the indentation. Lines 113-131 are tab-indented consistently with the rest of the file. Line 132 is a stray tab and two spaces. Lines 133-140 begin at **column zero** -- `
`, `
Jarvis Hosting`, then three more at one tab. The heredoc's contents were hand-edited by someone pasting links in, without matching the surrounding style, in at least two separate sittings (the four `.ikonboard.com` support links are one indentation cohort; Jarvis Hosting, iBSkins, iBHackers, and myIkonboard are another). Note the mixed quoting on line 129 too -- `href="http://help.ikonboard.com/popup/faq.php"` uses double quotes where every other link in the block uses single quotes. Another paste. **This file is the newest thing in the distribution**: mtime `2002-11-25 05:05:08` UTC (late evening 11/24/2002 US Central), against a `Sources.tar` archive mtime of `2002-11-24 06:38:48` -- the content is *newer than the archive that contains it* by 22h 26m 20s (`out_provenance.txt` section 2.1). The tarball was built, then this one file was replaced inside it, then the box shipped. **[INFERENCE]** Whatever this last edit was, it was worth breaking a completed build for, four months after development had otherwise stopped. Given that the file's distinguishing content is the network-links block, and given that the block's indentation shows it was assembled by paste in multiple passes, the most economical reading is that the final act of Ikonboard 3.1.1's production was updating the vendor's cross-promotion links. I cannot prove it -- there is no second copy of `Menuadmin.pm` in the tree to diff against. For completeness, `Menuadmin.pm` is also listed in `Upgrading\to_3.1.1 from 3.1.0\readme.txt` among the files updated between 3.1.0 and 3.1.1 **[DOC]**, so the file was already a moving part. #### 6.2 The eight destinations What each host was, with evidence: **`members.ikonboard.com` -- iB Member Center.** A registration-gated download and support portal. Archived 12/13/2002 **[ARCHIVE]**: > Welcome to the Ikonboard Member Center. This service uses the Ikonboard Support Forums > database for your convenience. Before attempting to login, you must have an account > registered on the forums. [...] **Download without Registering** -- If you wish to > download Ikonboard without registering you may do so here. If you do not register, you > will not be able to take advantage of the support tickets or other special services > available by registering on the community forums and logging into this member center. A lead-capture funnel around a free product, with an explicit opt-out. Single sign-on against the support forum's member table -- which is to say, against an Ikonboard. `members.ikonboard.com/admin_guide/` (the first link in the block) was the admin control panel documentation; `Upgrading\to_3.1.1 from 3.1.0\readme.txt` step 4 instructs the operator to unpack `admin_CP.zip` into `iB_html/non-cgi/help_admin` **[DOC]**, so the admin guide also shipped locally. **`help.ikonboard.com/popup/faq.php` -- ikonboard FAQ.** A PHP application. Ikonboard's own support FAQ ran on PHP while the product it documented was Perl. **[INFERENCE]** Not significant on its own -- plenty of shops mix -- but see section 2.9.3, and see the 06/12/2002 JEG headline "Jeg releases ib 3.1; **ibp php releases to follow**" **[ARCHIVE]**. **`forums.ikonboard.com` -- iB Support Forums.** The community support board, running Ikonboard. Named as the first line of support in Mecham's 08/30/2001 Read_Me **[DOC]** and in `Install_Guide.html` **[DOC]**, which also mentions "live support" and "fill out a support ticket." **`hosting.jarvisgroup.net` -- Jarvis Hosting.** The parent company's web hosting business, cross-sold in the admin panel of its free forum software. On the ikonboard.com front page it appears as a rotating page sponsor: "Page Sponsor: Looking for reliable web hosting? Try J-HOST!" **[ARCHIVE]** (12/10/2002). This is the only non-`ikonboard.com` host in the block that is unambiguously a revenue product. **`ibskins.ikonboard.com` -- iBSkins.** Skin distribution. The 12/10/2002 ikonboard.com front page describes it **[ARCHIVE]**: > IB Skins - Customize your Ikonboard with a variety of different looks from gaming skins > to professional skins. Our users and our dedicated staff create some of the most amazing > skins on the 'Net for your Ikonboard. The 10/17/2002 staff roster has a dedicated "iB Skinner" group (Akuma, neliconcept, Nimrod44) **[ARCHIVE]**. My attempt to retrieve an `ibskins.ikonboard.com` capture returned an IIS 404 page, so I have the vendor's description but not the site itself. **`www.ibhackers.com` -- iBHackers.** The modification community, on a separate domain. Archived 10/10/2003 **[ARCHIVE]**, tagline "iB Hackers [ the place ib developers come for answers ]". It ran a categorized **Hack Database** -- for 3.1.x, 3.0.x, and 2.x.x, split into Addons, Design/Layout, Admin CP, Bug Fixes/Patches, Converters, Languages, Skins -- plus a Graphic Database (Avatars, Pips, Post Buttons, Smilies), and a forum with 5,914 topics and 20,147 replies (26,061 total posts) at time of capture. The vendor's own description **[ARCHIVE]** (12/10/2002): > IB Hackers - An improved front page, with the addition of plug-in support, improved > features, flexible database abstraction layer, themes and a new script database. This > is where our users come to showcase their technical knowledge of Ikonboard. The Ikonboard staff page carried "iBH Leader" and "iBH Staff" groups on both the 06/05/2002 and 10/17/2002 rosters **[ARCHIVE]** -- iBHackers was a vendor-operated property on an arm's-length domain, not an independent fan site. **[INFERENCE]** This is the institutional counterpart to `Tools\writing_hacks\` (section 2.4.5) and to the license's modification clauses (section 2.5.2). The vendor supplied an SDK, hosted the resulting hacks in a categorized database, staffed the site, and reserved the legal right to absorb any of it. `Sources/Warn.pm` is what that pipeline looks like from the far end. **`www.myikonboard.com` -- myIkonboard.** A hosted-forum SaaS. Archived 12/04/2003 **[ARCHIVE]**: > MyIkonboard is a remotely hosted interactive community version of industry leader > Ikonboard, the forum software used by millions on web sites across the globe. Don't have > your own server? No problem! [...] (Average setup 2min.) > > **Proudly Hosting 31353 PHP MyIkonboards!** Tiering: free ad-supported; "MyIB - Economy" at $5.99/month to remove ads and unlock skinning; "MyIB - Pro" at $14.95/month for "a fully-featured **Ikonboard v3.0.2** on your own iB Pro server." Two things in that copy are important. First, this -- not license fees -- is where the recurring revenue was. Second, **"31353 PHP MyIkonboards"**: the free tier of the vendor's own hosted service was running a **PHP** codebase in December 2003, while the paid tier ran Perl Ikonboard 3.0.2. **[EXTERNAL]** This corresponds to "Project Mongoose," a commissioned PHP rewrite begun in 2002-2003 which reached release-candidate stage and whose developers departed in February 2003 without a public release; a version of it is reported to have powered myIkonboard. The 12/04/2003 capture is consistent with that. **[UNVERIFIED]** I could not confirm the Mongoose-to-myIkonboard connection from a primary source; the capture proves only that myIkonboard's free tier was PHP. **The block's absences.** The admin panel does *not* link to two properties that the public site did: `store.ikonboard.com` ("iB Gear" -- merchandise) and `win.ikonboard.com` (hosting "BanX" and a chat service). Both appear in the ikonboard.com navigation on 10/17/2002 and 12/10/2002 **[ARCHIVE]**. **[INFERENCE]** T-shirts did not make the administrator's sidebar; hosting, skins, hacks, and the SaaS did. #### 6.3 Jarvis Entertainment Group itself The archived ikonboard.com sidebar is the best surviving portrait of the parent company, and it is not what a reader expects behind a forum script. Every page in 2002 carried **[ARCHIVE]** (12/10/2002 capture): ``` Stock Information JRVE [ news ] [ price ] Corporate News Jeg releases ib 3.1; ibp php releases to follow June 12 2002, 08:53am Jarvis network news 5/21/02 May 21 2002, 08:30am Jarvis corporate 5/20/02 May 20 2002, 09:49am Jarvis network 5/03/02 May 03 2002, 11:15am Jrve news 2/19/02 Feb. 19 2002, 08:14am Jarvis Entertainment CEO Featured on Wall Street Reporter; Windows to Wall Street Interviews to Air Beginning December 10th 2001-12-10 Jarvis Entertainment Group Acquires Controlling Interest in COMC; Approves Manufacturing Division Spin-Off and Dividend 2001-12-06 Jarvis Entertainment Announces Film Production Deal for ``The Turnaround''; Pre-Production Underway 2001-11-21 JEG Release iB3; The Leader in Community Building Software 2001-11-05 Jarvis Network Traffic For Last Hour - This Site: 590 - Entire Network: 19,762 ``` The stock links point to `pinksheets.com/quote/quote.jsp?symbol=jrve` **[ARCHIVE]**. JEG was a **publicly traded company quoted on the Pink Sheets under JRVE**, whose corporate news in the fifteen months around Ikonboard 3's release consisted of a film production deal, a controlling-interest acquisition with a manufacturing-division spin-off and dividend, a CEO media appearance, and -- one item among them -- the release of a forum script. The myIkonboard capture a year later carries a longer JEG news list **[ARCHIVE]** (12/04/2003): "Jarvis Entertainment completes acquisition of RET," "JRVE Level II Quotes," "Jarvis Broadband goes residential," "Lights....Camera.....Action," "Dlink Releases Case Study On Jarvis," "Jarvis Inks Deal with Sprint," "BEACON Community Network completed," "The Sun, the Beach, the Wi-Fi," "D-Link's AirPlus Powers Largest Texas WiFi." **[INFERENCE]** Jarvis Entertainment Group was a small-cap diversified holding company -- film, broadband, municipal Wi-Fi, hosting, acquisitions -- with Ikonboard as one asset in a portfolio, valued substantially for the traffic and the installed base it represented. The "Jarvis Network Traffic For Last Hour" counter on every Ikonboard page (19,762 network-wide, 590 for ikonboard.com in the 12/10/2002 capture) is the metric of a company that thought of its properties as an audience. The 50,000-shares-of-common-stock story about Mecham's sale (section 2.3.2, unverified) is at least *consistent* with this profile: a company that paid in paper. **[EXTERNAL]** Mecham is reported to have said he could not liquidate the shares. **[EXTERNAL]** John D. Jarvis was president and CEO. JEG is reported to have been founded in 1999. #### 6.4 The block's real function Assembling sections 2.5 and 6: the license makes the retention of "any other domains existing in the software at time of download" a condition of the free license (`license.html:87`); the admin panel embeds eight such domains at the top of every administrator's sidebar (`Menuadmin.pm:128-138`); those domains lead to a hosting business, a paid SaaS, a merchandise-adjacent community, and a lead-capture download portal; and removing the visible branding costs $250. **[INFERENCE]** The Network Links block is the business model, rendered. Ikonboard 3.1.1 was not sold. It was distributed free to acquire administrators, and administrators were the funnel into hosting, myIkonboard subscriptions, and network traffic for a public company that reported traffic to shareholders. That the last edit anyone made to the product was to this block is, on this reading, exactly right. --- ### 2.7 Vulnerabilities and reputation #### 7.1 The premise, corrected The best-known remote code execution issue in Ikonboard 3.1.1 is **not** in the search functionality and **not** in the DBM backend. It is in the *language-file loader*, in the core library, and it is triggered by a cookie. There is a separate, later, and less severe SQL injection issue in search (section 2.7.5). I checked for a search/DBM RCE specifically and found none; the searches returned only the cookie issue and the SQL injection. **[UNVERIFIED]** If a search/DBM RCE advisory for Ikonboard exists, I did not locate it, and nothing in this chapter should be read as confirming one. #### 7.2 The bug, in the shipped source `Sources/Lib/FUNC.pm:180-209` **[CODE]**, complete and verbatim with line numbers: ```perl 180 ############################################################ 181 # LoadLanguage: 182 # Simply loads the required language file from disk, based on 183 # the users language choice 184 ############################################################ 184 sub LoadLanguage { 185 my ($obj, $area) = @_; 186 my ($lang, $default); 187 local $@; 188 189 # Make sure the cookie data is legal 190 if ($iB::COOKIES->{$iB::INFO->{'COOKIE_ID'}.'lang'}) { 191 $iB::COOKIES->{$iB::INFO->{'COOKIE_ID'}.'lang'} =~ s/^([\d\w]+)$/$1/; 192 } 193 194 $default = $iB::COOKIES->{$iB::INFO->{'COOKIE_ID'}.'lang'} 195 || $iB::INFO->{'DEFAULT_LANGUAGE'} 196 || 'en'; 197 198 # Quick check to make sure the directory exists 199 200 unless (-d $iB::INFO->{IKON_DIR}."Languages/$default") { 201 $default = 'en'; 202 } 203 204 my $code = 'require '. "\"$default/" .$area. '.pm"; $lang ='. $area. '->new();'; 205 eval $code; 206 207 $obj->cgi_error("Could not access the language file: $@") if $@; 208 return $lang; 209 } ``` Line 191 is the defect, and the comment above it at line 189 states the intent that the code does not achieve: ```perl # Make sure the cookie data is legal $iB::COOKIES->{...'lang'} =~ s/^([\d\w]+)$/$1/; ``` `s/^([\d\w]+)$/$1/` is a **substitution that replaces a matched string with itself.** If the cookie contains only word characters, the pattern matches and the value is replaced by an identical value: no change. If the cookie contains anything else -- a slash, a null byte, a semicolon, a quote -- the pattern **fails to match**, the substitution does nothing, and the original hostile value survives untouched. The author appears to have believed that a `s///` with an anchored capture acts as a filter. It does not. It is a no-op in both branches. Perl reports the failure only through the operator's return value, which is discarded. The tainted value then flows, with no further validation, into: - line 194: `$default`, unconditionally, because a non-empty string is truthy; - line 200: a filesystem path -- the only real check in the routine, an `-d` directory test, which is bypassable in Perl 5 of this era by embedding a null byte so that the C-level `stat()` sees a truncated path; - line 204: **a string that is passed to `eval`** at line 205. `$default` is interpolated into `'require ' . "\"$default/" . $area . '.pm"; $lang =' ...` and the resulting Perl source is executed. Attacker-controlled bytes reach a Perl string `eval`. That is arbitrary code execution as the web server user, from an unauthenticated HTTP request, requiring only a crafted `Cookie:` header. The same idiom appears a second time in the same file, ninety lines earlier -- `Sources/Lib/FUNC.pm:99-104` **[CODE]**: ```perl sub LoadSkin { my $obj = shift; my $sid = $iB::IN{'sid'} || $iB::COOKIES->{$iB::INFO->{'COOKIE_ID'}.'skin'}; # Make sure it only contains a number $sid =~ s/^(\d+)$/$1/; ``` Identical mistake, identical comment structure ("Make sure it only contains a number"), identical no-op. **[INFERENCE]** This was not a slip. It was a *believed-correct sanitization idiom* used by whoever wrote this library, applied at least twice, and neither instance was caught in review -- because, per section 2.4.4, there was no review. `Sources/Lib/FUNC.pm` was last modified `2002-07-13 23:47:08` **[CODE]**, two days before the last development activity in the tree. The vulnerable line shipped in that state. #### 7.3 Disclosure **[EXTERNAL]** The timeline, from the mailing-list archives themselves: | Date | Event | |------|-------| | **01/26/2003** | **Nick Cleaton** notifies the vendor ("The Jarvis Group / ikonboard.com"). | | **04/02/2003** | Cleaton discloses publicly on the **vuln-dev** list: "IkonBoard v3.1.1: arbitrary command execution." Fix status: **"None available."** Affected: "IkonBoard 3.1.1 and likely earlier versions." He includes a proof-of-concept that triggers a syntax error in the `eval` -- demonstrating injection -- and deliberately withholds a working exploit. | | **04/03/2003** | **Adam Gilmore** follows up on vuln-dev. He had previously expressed skepticism, noting that "a require fails on a directory and causes an error, preventing the rest of the code being evalled." He now reports having found a bypass and a working proof of concept that "can execute arbitrary perl." | | 05/13/2003 | **Ikonboard 3.1.2 released** -- *not* a fix for this issue (section 2.7.6). | | **09/2003** | Cleaton re-reports on **Bugtraq**: "IkonBoard 3.1.2a arbitrary command execution." Same vulnerability, still present in the successor release. He publishes the patch (below) himself, plus an `ibfix.cgi` to apply it and a detector script. | | 09/2003 | A **working proof-of-concept exploit** is posted to Bugtraq for 3.1.1 and 3.1.2a, dumping the target server's environment variables. The payload uses null bytes and URL encoding to escape the expected context and inject Perl. | | **09/22/2003** | **CVE-2003-0770** published. | Dates in this table are the coordinator-verified figures checked against NVD and the original advisories. Note that the archived list postings display in the poster's local time: the seclists rendering of Cleaton's disclosure reads "April 1, 2003" and Gilmore's follow-up reads "Friday, April 4, 2003, 07:48:50 **+1000**" -- Australian eastern time, i.e. 04/03/2003 21:48 UTC. The verified 04/02 and 04/03 dates are the ones to cite; the one-day spreads are timezone rendering, not disputed facts. Cleaton's September patch **[EXTERNAL]** targets precisely the two lines identified in section 2.7.2 -- and the correspondence with this tree is exact: - *Line 104*: replace `$sid =~ s/^(\d+)$/$1/;` with `$sid =~ s/^(\d+)$/$1/ or die 'invalid sid value';` - *Line 192*: replace with `$iB::COOKIES->{$iB::INFO->{'COOKIE_ID'}.'lang'} =~ s/^([\d\w]+)$/$1/ or die 'invalid lang cookie value';` The fix is to *use the return value of the substitution* -- exactly the thing the original code discards. **Line 104 in Cleaton's 3.1.2a patch is line 104 in this 3.1.1 tree**, character for character. The `lang` line is 192 in his 3.1.2a and 191 here -- a one-line offset. **[INFERENCE]** Between 3.1.1 and 3.1.2a, `Sources/Lib/FUNC.pm` gained exactly one line somewhere between line 104 and line 191, and nothing in that region was otherwise restructured. The security-relevant part of the core library was, functionally, unchanged across two releases and eight months. #### 7.4 CVE-2003-0770 **[EXTERNAL]** Confirmed identifier and details: | Field | Value | |-------|-------| | CVE | **CVE-2003-0770** | | Published | **09/22/2003** | | Description | `FUNC.pm` in IkonBoard does not properly cleanse the `lang` cookie when it contains illegal characters, allowing remote attackers to execute arbitrary code when the cookie is inserted into a Perl `eval` statement. | | Affected | IkonBoard 3.1.2a and earlier, explicitly including 3.1.1 | | CVSS v2 | 7.5 | | Exploit | Exploit-DB 22499 and 22500, "IkonBoard 3.1 - Lang Cookie Arbitrary Command Execution (1)" and "(2)" | | Bugtraq | `marc.info/?l=bugtraq&m=106381136115972&w=2` | The vulnerability also acquired an IDS signature. **[EXTERNAL]** Juniper's IPS signature database carries `HTTP:CGI:IKONBOARD-BADCOOKIE`, describing an IkonBoard 3.1.1 arbitrary command execution issue "due to insufficient sanitization performed on user supplied cookie data." A vulnerability with a vendor IDS signature named after it is a vulnerability that was being exploited in volume. #### 7.5 The rest of the record **[EXTERNAL]** Other identifiers associated with Ikonboard 3.1.x, with the confidence I can attach to each: | CVE | Confirmed | Detail | |-----|-----------|--------| | **CVE-2003-0770** | **yes** | The `lang` cookie RCE. `FUNC.pm` does not cleanse the `lang` cookie before it reaches a Perl `eval`. Affects **"IkonBoard 3.1.2a and previous versions,"** so 3.1.1 included. Published 09/22/2003. Exploit-DB 22499, 22500. See above. | | **CVE-2002-0328** | **yes** | Cross-site scripting via JavaScript in an `[img]` tag. Originally reported against **Ikonboard 3.0.1**; **also reported against 3.1.1**. This is the parent of CVE-2002-2230. | | **CVE-2002-2230** | **yes** | A variant of CVE-2002-0328: XSS via a **private message** containing a `javascript:` URL inside an IMG tag where the URL ends in `.gif` or `.jpg` -- defeating naive extension-based filtering of image URLs. **Affects 3.1.1.** Severity medium. **[UNVERIFIED]** publication date. | | **CVE-2002-2231** | **partly** | XSS in **Ikonboard 3.1.1** -- arbitrary script or HTML injected via a `javascript:` URL in a **photo URL** or in an **X-Forwarded-For header**. I have the description from a CVE aggregator; **I could not confirm the publication date** or the original advisory. | | **CVE-2004-1406** | **yes** | SQL injection in `ikonboard.cgi` in **Ikonboard 3.1.0 through 3.1.3**, via the `st` or `keywords` parameter. Published 12/31/2004 by NVD. High severity. References Secunia advisory 13513 and SecurityFocus BID 11982. **This is the search-related issue** -- `keywords` is the search input -- and it is SQL injection, not code execution, and it postdates 3.1.1's life by two years. | | **CVE-2001-0076** | context only | `register.cgi` in Ikonboard **2.1.7b and earlier**. Predecessor product; noted for completeness. | The `X-Forwarded-For` vector in CVE-2002-2231 is directly visible in this tree. `ikonboard.cgi:317` **[CODE]**: ```perl $iB::IN{'IP_ADDRESS'} = $ENV{'HTTP_X_FORWARDED_FOR'} || $ENV{'REMOTE_ADDR'}; ``` followed at line 324 by: ```perl ($iB::IN{'IP_ADDRESS'}) = $iB::IN{IP_ADDRESS} =~ /^(.+?)(,|$)/; ``` A client-controlled header is preferred over the socket peer address, and the only processing applied is splitting on the first comma -- `(.+?)` matches anything. The resulting "IP address" is displayed to moderators and administrators. That is a plausible mechanism for the reported XSS, and it is present at `ikonboard.cgi:317,324` in the shipped 3.1.1 dispatcher. **[INFERENCE]** -- I am matching a CVE description to code, not citing an advisory that names these lines. #### 7.6 3.1.2, and the end of 3.1.1's life **[ARCHIVE]** The release date of 3.1.2 comes from the iBHackers front page, captured 10/10/2003, in a news post by staff member David-iB: > **New staff member!** > Posted on May 14 2003, 04:14 by David-iB > I would like to welcome skelm to the iBH team. He will be involved in a specialised > project for the ibh community, althrough details will not be released at this moment in > time. For thoes of you who don't know, **ib3.1.2 was released yesterday!** Posted 05/14/2003 in board-local time, referring to "yesterday" -- **Ikonboard 3.1.2 was released on 05/13/2003**. This is the operational end of 3.1.1's life: after this date, 3.1.1 is the superseded version. **3.1.2 was not a fix for CVE-2003-0770, and nothing in this chapter should be read as saying it was.** That is worth stating flatly, because it is the natural assumption -- a release six weeks after a public RCE disclosure looks like a response -- and it is false. Cleaton's September 2003 Bugtraq post is titled "IkonBoard 3.1.2a arbitrary command execution," names 3.1.1 **and** 3.1.2a as affected, and supplies the two-line patch himself **[EXTERNAL]**. The CVE's own affected-version string is "IkonBoard 3.1.2a and previous versions." The successor release, and the point release after *that*, both shipped the vulnerable `FUNC.pm`. The interval, laid out: ``` 01/26/2003 vendor privately notified day 0 04/02/2003 public disclosure; fix status "None available" + 66 days 05/13/2003 3.1.2 ships -- still vulnerable + 107 days 09/2003 3.1.2a still vulnerable; the REPORTER publishes the two-line patch, an ibfix.cgi to apply it, a detector script, and a working exploit + ~225 days 09/22/2003 CVE-2003-0770 assigned + 239 days ``` **Eight months from private notification to a public fix -- and the fix was written by the person who reported the bug, not by the vendor.** In that window the vendor shipped two releases and carried the patch in neither. **[INFERENCE]** Read against sections 2.4 and 6, this is what a volunteer development organization with an absentee corporate owner looks like under security pressure. All the apparatus of a supported commercial product was in place -- a staffed support forum, a hack database, a roster of a dozen, a public bug tracker at `bugs.ikonboard.com`, live support and support tickets advertised in `Install_Guide.html`, and a $250 license tier that explicitly bundled "30 days of priority technical support." None of it converted a private, correct, reproducible pre-authentication RCE report into a patched release in eight months. On the October 2002 roster, one of six developers had a company email address (section 2.4.3); whether anyone still held responsibility for the core library by 2003 is not something the surviving record answers. The consequences compound. A cookie-triggered pre-auth RCE, with a published working exploit and a vendor IDS signature named after it (`HTTP:CGI:IKONBOARD-BADCOOKIE`, section 2.7.4), in a product whose entire value proposition was *free and easy to install on cheap shared hosting*, attacks the product exactly where it lives: shared-host operators blacklist software that gets their servers rooted, and an IDS signature makes that blacklisting automatic and durable. This is the most damaging single event in the product's reputation, and it lands in 2003 -- the year *before* the corporate reorganization, litigation, outages, and team departures that section 2.8 describes. By the time the ownership churn began, the software's security standing was already spent. #### 7.7 Why the architecture invited it Two design decisions in Ikonboard 3 made this class of bug likely, and both are visible in the tree: **1. Dynamic `require` driven by user input.** Ikonboard 3's skin, language, and module loading are all string-built and `eval`'d or `require`'d at runtime. `LoadLanguage` (`FUNC.pm:204`) builds Perl source from `$default`. The dispatcher does the same thing by design -- `ikonboard.cgi:475` region, described in its own comment **[CODE]**: ```perl # Nice little hack to save writing loads of subroutines for each action. # It builds the code from the %Mode hash, depending on the contents of 'act' # For example, the eval'd code may look like: # use Topic; ``` The dispatcher's version is safe because `%Mode` is a fixed hash and unknown `act` values are forced to `BoardIdx` (`ikonboard.cgi:473-474`). `LoadLanguage`'s version is unsafe because its input is a cookie. Same idiom, opposite outcomes, in the same codebase -- the pattern was house style, and its safety depended entirely on whoever applied it remembering to constrain the input. **2. `use strict` at 74%.** `out_delta.txt` section 2.1 records the jump from 0% of 2.1.9's files to 74% of 3.1.1's. That is a large improvement and it is genuine. But `FUNC.pm` -- the file with both no-op sanitizers -- does declare `use strict`, which is the point: `use strict` catches typos and symbolic references. It does not catch a substitution whose return value you ignore. The modernization of Ikonboard 3 was real at the level of Perl hygiene and did not extend to input validation. `out_taint.txt` in this teardown examines that gap systematically; this chapter only notes that the most-cited vulnerability in the product's history sits in a `use strict` file, three lines below a comment asserting that the data has been made legal. --- ### 2.8 The end The decline has two clocks running at once, and they should be read together. The **technical** clock is section 2.7: by September 2003 the vendor had been sitting on a reported pre-authentication remote code execution bug in its flagship product for eight months, had shipped two releases without fixing it, and had been overtaken by the reporter, who published the patch and then the exploit. The **corporate** clock starts five months later, in February 2004, and is the subject of this section. The order matters: Ikonboard's security reputation was spent *before* the ownership churn began, not because of it. What follows compounded an existing problem rather than creating one. #### 8.1 Ikonboard after 2002 **[EXTERNAL]** The ownership history, from encyclopedia sources. I flag it as such because the primary documentation for most of it is gone with the sites: | Date | Event | |------|-------| | 02/2004 | Jarvis Entertainment Group renamed / reorganized as **Westlin Corporation**. | | 2004-2005 | Litigation: former JEG chairman **John Jarvis** moves to reclaim ownership of Ikonboard. Server outages and staff departures follow. The development team leaves to pursue **Infinite Core Technology**. | | 10/2005 | "Ikonboard releases were no longer available for download." | | 10/28/2005 | Ownership formally transferred to **John Jarvis**; parent company becomes **Pitboss Entertainment**; site down until December. | | 01/30/2006 | **Ikonboard 3.1.3** released -- the first release in nearly three years. Adds a "Human Readable Image" CAPTCHA on registration and an admin update center. | | 03/2006 | Assets consolidated under **Level 6 Studios**. | | 02/2006 | 3.1.4, bug fixes. | | 05-06/2006 | 3.1.5. (Sources differ: 05/30/2006 or 06/02/2006. **[UNVERIFIED]**) | | 09/10/2006 | "The Ikonboard Team" -- the volunteers behind 3.1.3-3.1.5 -- depart en masse and found **IkonForums**, releasing IkonForums 1.0.0 in September 2006. Ikonboard 3.2, in development since 2003, is abandoned. | | 09/2009 | Ownership passes to **Joshua Johnson**, via **Ikonboard Services Inc.** Johnson had been managing the site on Jarvis's behalf. | | 06/2010 | **3.1.5a** -- minor updates and bug fixes. The last release of the Perl Ikonboard. | | 2017 | "As of January 2017 no new software releases have been made, and their website has ceased to exist." | The pattern from 2004 onward is the same one twice: a volunteer team carries the product, the corporate layer above it destabilizes, the team leaves and forks. Infinite Core Technology in 2004-2005; IkonForums in 2006. Each time, the people who actually wrote the code walked away from the company that owned it -- which is, note, precisely what Mecham and Warner did in February 2002. Ikonboard lost its development capability three times in five years for the same structural reason. **[EXTERNAL]** A PHP successor was promised repeatedly and never publicly delivered. The 12/10/2002 JEG headline read "Jeg releases ib 3.1; **ibp php releases to follow**" **[ARCHIVE]**; "Project Mongoose" (2002-2003) produced "Ikonboard PHP" and "iBPLite" release candidates whose developers left in February 2003; myIkonboard's free tier ran a PHP codebase by December 2003 **[ARCHIVE]**; no PHP Ikonboard was ever released to the public. The company announced the transition to the winning platform in mid-2002 and spent the rest of its existence not completing it. #### 8.2 The domain, decaying The Internet Archive's index for `ikonboard.com` gives a mechanical picture of the end **[ARCHIVE]** (CDX query, `url=ikonboard.com`, collapsed to one capture per month): ``` 2001-03 .. 2007-12 HTTP 200, regular monthly captures -- live site 2008 (gap) 2009-09 .. 2014-12 HTTP 200, intermittent -- live site 2015-01-01 10:28:32 HTTP 200 -- LAST 200 with content 2015-02 .. 2017-12 HTTP 302 / warc revisit, every capture -- redirecting 2018-01 .. 2018-05 HTTP 200 -- content again 2018-07 .. 2018-09 HTTP 500 -- broken 2018-10 .. 2021-03 HTTP 302 / revisit -- redirecting ``` The 01/01/2015 capture is still a real Ikonboard site **[ARCHIVE]**: > **Ikonboard** -- Comprehensive web bulletin board system. > > Ikonboard is a comprehensive web bulletin board system. It transcends the limits imposed > by other bulletin board software, allowing you to create a truly unique community on > your website. From it's extensive template and skins features to it's complete > multi-lingual support, your members will keep coming back for more. > > With well over 1 million active boards operating across the web and over 50,000 > downloads in the past few months, Ikonboard is truly one of the most popular systems > available. > > (c) 2014 Ikonboard Three navigation items -- About, Download, Features -- and a "No categories" stub from an empty CMS. The marketing copy claiming a million active boards and 50,000 recent downloads was, by 2015, thirteen years stale; the last release had been in 2010 and the last major one in 2006. The 01/2018 capture is the identical page with the copyright bumped to "(c) 2018 Ikonboard" **[ARCHIVE]**, then the site 500s in mid-2018 and stops resolving to content thereafter. **[INFERENCE]** The site did not die; it was left running. A hosting bill kept a three-page brochure and a copyright-year script alive for years after the software behind it had stopped existing. The 302s from 02/2015 and again from late 2018 indicate the domain being pointed elsewhere; the brief 2018 return of the same page suggests it changed hands and was restored, not revived. **[EXTERNAL]** The domain is reported to have moved away from Ikonboard Services Inc. and Joshua Johnson's control after 2017. #### 8.3 The extinction of the Perl-CGI forum Ikonboard 3.1.1 was released into the last months of its entire software category's viability. The dates line up almost too neatly: | Date | Event | Stack | |------|-------|-------| | 1997 | WWWThreads first written | Perl **[EXTERNAL]** | | 1999 | Ikonboard 0.9 beta | Perl, flat file **[EXTERNAL]** | | 1999 | Infopop UBB.classic dominant on large sites | Perl, flat file **[EXTERNAL]** | | 02/2000 | Limm and Percival begin **vBulletin** -- explicitly because UBB, "written in Perl using a flat-file database, could not always cope with the number of users they had" | PHP + MySQL **[EXTERNAL]** | | 2000 | WWWThreads PHP version | PHP **[EXTERNAL]** | | 2000 | **YaBB** released | Perl **[EXTERNAL]** | | 2000 | vBulletin 1 released commercially by Jelsoft after Infopop declines to buy it | PHP + MySQL **[EXTERNAL]** | | 2001 | Infopop acquires WWWThreads, renames it **UBB.threads** | PHP **[EXTERNAL]** | | 04/2001 | Ikonboard sold to JEG | Perl **[EXTERNAL]** | | **04/04/2002** | **phpBB 2.0.0 "Super Furry"** released after 14 months of work | PHP + MySQL **[EXTERNAL]** | | **02/2002** | Invision Power Services founded; IPB written from scratch | PHP + MySQL **[EXTERNAL]** | | **06/12/2002** | **Ikonboard 3.1** released; "ibp php releases to follow" | **Perl** **[ARCHIVE]** | | 2002 | **YaBB forked to YaBB SE**, a complete PHP rewrite, later renamed **Simple Machines** | Perl -> PHP **[EXTERNAL]** | | 07/15/2002 | Ikonboard 3.1.1 development ends | Perl **[CODE]** | | 11/24/2002 | Ikonboard 3.1.1 repackaged | Perl **[CODE]** | Every serious competitor either started on PHP+MySQL or migrated to it between 2000 and 2002. vBulletin exists *because* Perl+flatfile did not scale. YaBB's own community forked it to PHP in 2002 and that fork became Simple Machines Forum. Infopop moved UBB to PHP. phpBB 2.0 -- free, PHP, MySQL -- shipped ten weeks before Ikonboard 3.1. Ikonboard 3 is, in this light, a very good answer to the wrong question. Its architectural achievements are exactly the ones the category needed and it delivered them in the losing language: a real database abstraction with five backends including MySQL, PostgreSQL, and Oracle (`Sources/iDatabase/`); server-side sessions (`Sources/Sessions.pm`); templates compiled to Perl for speed (`Skin/*.cfg` -> `*.pm`); mod_perl support (`Sources/iPerl/mod_perl.pm`); a per-backend search API (`Sources/Search/API/`); attachments, member groups with permission masks, ARC4-encrypted stored credentials (`out_delta.txt` sections 2.4 and 5). It went from 43 files to 179, from 15,629 lines to 72,943, from 88 subroutines to 1,624, and from 0% to 74% `use strict`. It was a serious rewrite by serious people. The structural problem it could not escape: - **Deployment.** PHP ran as a module in the web server. Perl CGI forked an interpreter per request. `Install_Guide.html` requires the operator to identify their cgi-bin, CHMOD files to 755, transfer `.pm`/`.cfg`/`.html` in ASCII mode and images in binary, run `perl_test.cgi` to discover their sendmail path, and unpack six `.tar` archives **[DOC]**. `Upgrading\to_3.1.1 from 3.1.0\readme.txt` is a five-step manual file-copy procedure **[DOC]**. phpBB was: upload, visit a URL. - **Hosting economics.** Shared hosts sold PHP+MySQL by default and restricted CGI. The installer defaults in this tree assume DB_File and flat files precisely because MySQL could not be assumed -- `Install_Guide.html` **[DOC]**: "A host that has installed Perl 5 or better with the DB_file module installed. Please note that although we offer MySQL databases for Ikonboard, that it is not a requirement." - **The modification community.** Forum software of this era lived on user customization. PHP was the language the customizers already knew. iBHackers had 26,061 posts in 2003 **[ARCHIVE]**; phpBB's MOD community was an order of magnitude larger. - **Its own vendor said so.** "ibp php releases to follow," 06/12/2002 **[ARCHIVE]**. **[INFERENCE]** Ikonboard 3.1.1 is the high-water mark of the Perl CGI forum: the most architecturally ambitious thing the category produced, shipped in the year the category lost. The line that survived was not the code -- it was the author, working in PHP under a different company name, on a product that quickly gathered the users of the one he left behind. --- ### 2.9 Timeline Every dated event established in this chapter, with its evidence class. Dates are mm/dd/yyyy. Tree timestamps are UTC. | Date | Event | Evidence | |------|-------|----------| | 09/1999 | Ikonboard 0.9 beta. Matt Mecham, Perl, flat file. Operating from ikondiscussion.com. | **[EXTERNAL]** | | 1999 | Jarvis Entertainment Group founded. John D. Jarvis, president and CEO. | **[EXTERNAL]** | | 02/2000 | vBulletin begun as a PHP+MySQL rewrite of Perl UBB, because UBB "could not always cope." | **[EXTERNAL]** | | 2000 | YaBB released (Perl). WWWThreads PHP version. vBulletin 1 released by Jelsoft. | **[EXTERNAL]** | | 03/2001 | ikondiscussion.com server crash; project moves to ikonboard.com. | **[EXTERNAL]** | | late 04/2001 | "Ikonboard officially joined the Jarvis Network." Mecham sells Ikonboard to JEG; consideration reported as 50,000 shares of common stock. Current release 2.1.8; 3.0 in beta. | **[EXTERNAL]**, share count **[UNVERIFIED]** | | 05/2001 | **iDatabase v1.0** written. "Developed for Ikonboard. Author: Matthew Mecham." | **[CODE]** `iDatabase/Driver/CSV.pm:6-9` | | 05/29/2001 | Mecham writes `perl_test.cgi` and its read_me; read_me signed `--Matt Mecham ()` / `29/5/01`. | **[DOC]** `Tools\HELP\read_me.txt` | | 05/29/2001 | Oldest file in the board tree: `cgi-bin/Data/.htaccess`. | **[CODE]** | | 06/2001 | **Ikonboard 2.1.9** era. "Copyright 2001 Ikonboard.com." "All files written by Matthew Mecham." Manufacturer: Gosport, Hants, UK. License governed by UK law, damages capped at GBP 1.00, de-branding GBP 200.00. | **[CODE]**, **[DOC]** | | 06/02/2001 | `cgi-bin/Data/index.html` and the placeholder `index.html` files -- earliest dated files in `out_provenance.txt`'s bracket. | **[CODE]** | | 06/03/2001 | ikonboard.com navigation shows "Ikonboard v3.0", a **Bug Tracker** at `bugs.ikonboard.com`, Member Center, Documentation, BBS Convertors, Chat. Team photo page: Matt, Heartcall (Ken), Soeren, Joe, Peter, Camikaze (Stewart). | **[ARCHIVE]** | | 07/19/2001 | `cgi-bin/Data/MemberTitles.pm` -- oldest Perl file in the tree. | **[CODE]** | | 08/13/2001 | `cgi-bin/Data/ib_data_file.dat`. | **[CODE]** | | **08/30/2001** | **Mecham signs the iB2->iB3 migration guide**: `$ Matt - 30 August 2001, 2:16am`. Describes iB3 as still in BETA and in use on the company's own support board. Ships unchanged in the 3.1.1 box. Redirect scripts in `Tool_Box/` same timestamp. **Left bound on his departure.** | **[DOC]** | | 10/26/2001 | `Tools\mod_perl\start_up.pl`. | **[DOC]** | | 10/30/2001 | `Tools\writing_hacks\module_template.pm` -- the hack-writing SDK. | **[DOC]** | | **11/05/2001** | JEG press item: "JEG Release iB3; The Leader in Community Building Software." | **[ARCHIVE]** | | 11/21/2001 | JEG announces a film production deal for "The Turnaround." | **[ARCHIVE]** | | 12/06/2001 | JEG acquires controlling interest in COMC; announces manufacturing spin-off and dividend. | **[ARCHIVE]** | | 12/10/2001 | JEG CEO featured on Wall Street Reporter / Windows to Wall Street. | **[ARCHIVE]** | | **12/18/2001** | **`license.html` written** -- the JEG license shipped with 3.1.1. Free personal and commercial; $250 de-branding; "not open source"; six modification conditions; contributor ownership assigned away; at-will termination "for any or no reason"; right to publish users; US law; Tomball, TX; phone 434-352-9311 (a Virginia area code). | **[DOC]** | | **02/2002** | **Invision Power Services founded** by Charles Warner and Matt Mecham, both ex-JEG. IBForums (later Invision Power Board) begun. PHP + MySQL. **Right bound on Mecham's departure.** | **[EXTERNAL]**, Mecham's own statement in a 04/22/2004 interview | | 02/19/2002 | JEG corporate news item "Jrve news 2/19/02." | **[ARCHIVE]** | | **04/04/2002** | **phpBB 2.0.0 released** after 14 months of work. Free, PHP, MySQL. | **[EXTERNAL]** | | 04/08/2002 | `# Added by LrdChaos 4/8/02` -- `Sources/Admin/Category.pm:535,695`. Earliest hand-dated contribution in the tree. | **[CODE]** | | 05/03, 05/20, 05/21/2002 | Jarvis network / corporate news items. | **[ARCHIVE]** | | 05/07/2002 | `## Added by LrdChaos 5/7/02 for watched topic` / `for topic watch` -- `Languages/en/ModerateWords.pm:21`, `Languages/en/TopicWords.pm:29`, `Sources/Admin/ModControl.pm:447,800`. | **[CODE]** | | 05/20/2002 | `cgi-bin/Data/MimeTypes.cfg`. | **[CODE]** | | 06/02/2002 | `cgi-bin/Data/SkinList.cfg`. | **[CODE]** | | **06/05/2002** | **Ikonboard staff roster captured.** "Ikonboard Network **Volunteer** Staff." Development Team: **Camil** (`ccollard@enter-net.com`), **KEVaholic00** (`kevaholic00@yahoo.com`), JayLittle, Jevon. Team Leaders include **LrdChaos** (`lrdchaos.idev@ikonboard.com`), Sly, Wedge, Redbaron, Malkavian, Brush, Benjamin Liger. Administrators: Gladiator, Fender, Quasi, Kaitou Ace. | **[ARCHIVE]** | | **06/12/2002** | **Ikonboard 3.1 released.** JEG headline: "Jeg releases ib 3.1; **ibp php releases to follow**" (08:53am). `INSTALL_DATA/board_rules.dat`, `cgi_path.html`, `cgi_url.html` all timestamped 06/12/2002 21:34. | **[ARCHIVE]**, **[CODE]** | | 06/13/2002 | `Data.tar` built (02:41:32). | **[CODE]** | | **06/14/2002** | `Upgrading\to_3.1.1 from 3.1.0\readme.txt` written -- the 3.1.0->3.1.1 manual upgrade procedure, listing 25 updated `Sources` files including `Menuadmin.pm`. **Places the 3.1.1 patch release in mid-June 2002.** | **[DOC]** | | 06/14/2002 | `Languages.tar` built (06:47:30) -- later overwritten in place; see 07/14. | **[CODE]** | | 06/16-17/2002 | `installer.cgi` (06/17 03:50 UTC). | **[CODE]** | | 06/20/2002 | `install_modules/*` (23:15); `Tools\rm_*.cgi`. | **[CODE]** | | 06/21/2002 | `Sources/ARC4.pm`; `Database.tar` contents. | **[CODE]** | | 06/22/2002 | `Database.tar` built (07:05:32). `Sources/iDatabase/Driver/Oracle.pm` (01:40). `Glossary.html`, `Install_Guide.html`, `Installer_Guide.html` all dated 06/22/2002. | **[CODE]**, **[DOC]** | | 06/23/2002 | `Sources/Calendar.pm` (16:28), `Sources/Legends.pm` (16:38), `Sources/NotePad.pm` (18:09), `Sources/Upgrade.pm` (18:52), `Sources/Warn.pm` (18:52). Four contributor modules in one day. `Tools\create_indexes.cgi`, `restore_admin.cgi`. | **[CODE]**, **[DOC]** | | 06/24/2002 | `Sources/Admin/WebRing.pm` (05:20) -- KEVaholic00's webring. `Sources/UserCP/Menu.pm` (19:10) -- the file whose copyright year was bumped but whose owner name was not. `Upgrading\from_3.0.x_to_3.1.1\mysql_table_updater\alter_table.cgi`. | **[CODE]**, **[DOC]** | | 06/26/2002 | `Upgrading\from_3.0.x_to_3.1.1\upgrade_info_mySQL.txt`. | **[DOC]** | | 06/27/2002 | Newest content inside `non-cgi.tar`. | **[CODE]** | | 07/01/2002 | `Sources/Admin/SQLclient.pm` (15:15) -- Nurlan Mukhanov's SQL client. | **[CODE]** | | 07/12/2002 | Skin `.cfg`/`.pm` sweep -- `PostView`, `PostersView`, `ProfileView`, `ReportView`, `SearchView`, `Universal`, `WarnView`, `PrintPageView`, `TopicView`. | **[CODE]** | | 07/13/2002 | `ikonboard.cgi` (01:33:34). `Sources/Admin/Tools.pm`. **`Sources/Sessions.pm` (21:20:54)** -- still stamped `(c)2001 Ikonboard.com`, `[ v3.0 ]`. `Skin/Default/RegisterView.*`. **`Sources/Lib/FUNC.pm` (23:47:08)** -- containing the no-op sanitizers at lines 104 and 191 that become CVE-2003-0770. | **[CODE]** | | 07/14/2002 | `Languages/en/PostWords.pm` (00:23:03) -- **newer than the `Languages.tar` that contains it by 29d 17h**. `INSTALL_DATA/tiker.html`, `news.html`. `Sources/Register.pm` (22:26:36). | **[CODE]** | | **07/15/2002** | **Development ends.** `Sources/SSI/Parser.pm` (00:20:14), `Skin/Default/Styles.pm` (00:39:40), **`Sources/Admin/ModControl.pm` (13:47:12)** -- the last file of the development run. | **[CODE]** | | **10/17/2002** | **Ikonboard staff roster captured again.** iB3 Development Team: Jevon, **KEVaholic00**, **Porter** (`porter@jarvisgroup.com`), Pysbird, simonpersson, Sanjeet Ganjam. Camil and LrdChaos no longer listed. Separate iB Skinner, iBH, and MYiSupport groups. | **[ARCHIVE]** | | **11/23/2002** | `readme.txt` written -- the top-level "Ikonboard v3.1.1" package inventory. ("Their Are 4 Directories and 4 files in this Package" -- sic.) | **[DOC]** | | **11/24/2002** | **Repackaging.** `Sources.tar` (06:38:48), `Skin.tar` (06:49:14), `non-cgi.tar` (06:52:12) all rebuilt. | **[CODE]** | | **11/24-25/2002** | **`Sources/Admin/Menuadmin.pm` (11/25 05:05:08 UTC = evening of 11/24 US Central)** -- the newest file in the distribution, 22h 26m *newer than the `Sources.tar` that contains it*. Its distinguishing content is the "Network Links" block (`:112-144`): `members.ikonboard.com/admin_guide/`, `help.ikonboard.com`, `forums.ikonboard.com`, `members.ikonboard.com`, `hosting.jarvisgroup.net`, `ibskins.ikonboard.com`, `ibhackers.com`, `myikonboard.com` -- with the paste-damaged indentation of a hand edit. **The last thing anyone did to Ikonboard 3.1.1.** | **[CODE]** | | 12/10/2002 | ikonboard.com front page captured: "Ikonboard 3.1 Services," JRVE pink-sheets ticker, network description of Ikonboard / IB Hackers / IB Skins / My Ikonboard, "J-HOST" page sponsor, "Jarvis Network Traffic For Last Hour - This Site: 590 - Entire Network: 19,762." | **[ARCHIVE]** | | 12/13/2002 | members.ikonboard.com captured: registration-gated download with an explicit "Download without Registering" opt-out; single sign-on against the support forum database. | **[ARCHIVE]** | | 2002 | **CVE-2002-0328** -- cross-site scripting via JavaScript in an `[img]` tag. Originally against Ikonboard 3.0.1, **also reported against 3.1.1**. **CVE-2002-2230**, its variant: XSS via a private message containing a `javascript:` URL in an IMG tag ending `.gif` or `.jpg`, **affecting 3.1.1**. **CVE-2002-2231** -- XSS via a `javascript:` URL in a photo URL or an `X-Forwarded-For` header, 3.1.1. Exact publication dates **[UNVERIFIED]**; placed here by CVE year. | **[EXTERNAL]** | | **01/26/2003** | **Nick Cleaton privately notifies the vendor** of the `lang` cookie arbitrary command execution issue in 3.1.1. | **[EXTERNAL]** | | **04/02/2003** | **Cleaton discloses publicly** on vuln-dev. Vendor fix status: **"None available."** PoC triggers a syntax error in the `eval`; working exploit withheld. | **[EXTERNAL]** | | **04/03/2003** | Adam Gilmore reports on vuln-dev that he has bypassed the `require`-on-a-directory constraint and has working arbitrary Perl execution. (Archived rendering shows 04/04 +1000, i.e. Australian eastern time.) | **[EXTERNAL]** | | **05/13/2003** | **Ikonboard 3.1.2 released** -- 15 weeks after the vendor was notified, and **not a fix for the RCE**; 3.1.2 and 3.1.2a both remain vulnerable. (Reported by iBHackers staff on 05/14/2003: "ib3.1.2 was released yesterday!") **End of 3.1.1's life as the current version.** | **[ARCHIVE]** | | **09/2003** | **Cleaton re-reports on Bugtraq** -- "IkonBoard 3.1.2a arbitrary command execution" -- and **publishes the two-line patch himself**, targeting `Sources/Lib/FUNC.pm` lines 104 and 192 in 3.1.2a (lines 104 and 191 in this 3.1.1 tree). Ships an `ibfix.cgi` to apply it and a detector script. Confirms 3.1.1 and 3.1.2a both vulnerable. A working exploit -- crafted `lang` cookie, null bytes, URL-encoded, dumping the server's environment -- is posted the same month. | **[EXTERNAL]** | | **09/22/2003** | **CVE-2003-0770 published.** CVSS v2 7.5. "FUNC.pm in IkonBoard **3.1.2a and previous versions** does not properly cleanse the 'lang' cookie... allows remote attackers to execute arbitrary code." Exploit-DB 22499, 22500. **Eight months, two releases, and no vendor patch.** | **[EXTERNAL]** | | 10/10/2003 | ibhackers.com captured: 5,914 topics / 20,147 replies / 26,061 posts, categorized Hack Database for 3.1.x, 3.0.x, 2.x.x. | **[ARCHIVE]** | | 10/2003 | John Jarvis ousted as CEO. | **[EXTERNAL]** | | 12/04/2003 | myikonboard.com captured: "Proudly Hosting **31353 PHP** MyIkonboards"; MyIB-Economy $5.99/mo, MyIB-Pro $14.95/mo with "Ikonboard v3.0.2." | **[ARCHIVE]** | | 02/2004 | JEG becomes **Westlin Corporation**. | **[EXTERNAL]** | | 04/22/2004 | Mecham interview published: "Invision Power Services was created in February 2002." | **[EXTERNAL]** | | **12/31/2004** | **CVE-2004-1406 published** -- SQL injection in `ikonboard.cgi` in 3.1.0-3.1.3 via the `st` or `keywords` parameter. Secunia 13513, SecurityFocus BID 11982. **The search-related issue.** | **[EXTERNAL]** | | 2004-2005 | Litigation over ownership; outages; the development team leaves for Infinite Core Technology. | **[EXTERNAL]** | | 10/2005 | Ikonboard releases no longer available for download. | **[EXTERNAL]** | | 10/28/2005 | Ownership transferred to John Jarvis; parent becomes **Pitboss Entertainment**. | **[EXTERNAL]** | | 01/30/2006 | **Ikonboard 3.1.3** -- HRI CAPTCHA on registration, admin update center. | **[EXTERNAL]** | | 03/2006 | Assets to **Level 6 Studios**. | **[EXTERNAL]** | | 02/2006 | 3.1.4, bug fixes. | **[EXTERNAL]** | | 05-06/2006 | 3.1.5. Exact date disputed between sources. | **[EXTERNAL]** / **[UNVERIFIED]** | | 09/10/2006 | "The Ikonboard Team" departs; founds **IkonForums**, releases 1.0.0. Ikonboard 3.2 abandoned. | **[EXTERNAL]** | | 09/2009 | Ownership to Joshua Johnson via **Ikonboard Services Inc.** | **[EXTERNAL]** | | 06/2010 | **3.1.5a** -- the last release of the Perl Ikonboard. | **[EXTERNAL]** | | 01/01/2015 | Last ikonboard.com capture serving real content -- a three-page brochure, "(c) 2014 Ikonboard," still claiming "well over 1 million active boards" and "50,000 downloads in the past few months." | **[ARCHIVE]** | | 02/2015 - 12/2017 | Every capture a 302 or a revisit. | **[ARCHIVE]** | | 01/2018 - 05/2018 | Content returns; identical page, "(c) 2018 Ikonboard." | **[ARCHIVE]** | | 07-09/2018 | HTTP 500. | **[ARCHIVE]** | | 10/2018 - 03/2021 | 302s and revisits; no content. | **[ARCHIVE]** | | 01/2017 | "No new software releases have been made, and their website has ceased to exist." Domain later changes hands. | **[EXTERNAL]** | --- ### 2.10 What could not be verified Stated plainly, so that later researchers know where to dig: 1. **The exact date Matt Mecham left Jarvis Entertainment Group.** Bounded to 08/30/2001 - 02/2002 by a shipped document and Mecham's own account. No resignation date, announcement, or last-commit date located. 2. **The consideration for the Ikonboard sale.** "50,000 shares of common stock" appears in encyclopedia sources without a citation I could follow. No SEC filing, press release, or statement by Mecham located. JEG's Pink Sheets ticker (JRVE) is confirmed **[ARCHIVE]**, so filings may exist. 3. **Bug #168.** `bugs.ikonboard.com` is confirmed to have existed **[ARCHIVE]** but no capture of an individual issue was found. What KEVaholic00 fixed at `ikonboard.cgi:342` is unknown beyond what the code does (appending the skin directory to `IMAGES_URL`). 4. **The exact rule that produced `Mor2001-e`** in `Sources/Admin/Index.pm:8`. One instance survives; the rule cannot be recovered from it. That it was an automated bulk edit is certain; what the pattern was is not. 5. **What changed in `Sources/Admin/Menuadmin.pm` on 11/24-25/2002.** The tree contains one copy. The network-links attribution is an inference from the block's paste-damaged indentation and its subject matter, not a diff. 6. **The 434 telephone number** in `license.html`. The Virginia/Texas mismatch is a fact; the connection to IPS's later Forest, Virginia location is a guess and is labeled as one throughout. 7. **Publication dates for the 2002 XSS issues.** CVE-2002-0328 (JavaScript in an `[img]` tag; originally 3.0.1, also reported against 3.1.1), CVE-2002-2230 (its private-message `javascript:`-URL-ending-in-`.gif`/`.jpg` variant, 3.1.1), and CVE-2002-2231 (`javascript:` URL in a photo URL or `X-Forwarded-For` header, 3.1.1) all have confirmed descriptions and confirmed affected versions, but I could not locate the original advisories or their publication dates. They are placed in the timeline by CVE year only. 8. **A "search functionality / DBM backend" RCE** in Ikonboard 3.1.1. Searched for directly; not found. The RCE of record is the `lang` cookie / `LoadLanguage` `eval` issue (CVE-2003-0770); the search-related issue of record is a 2004 SQL injection (CVE-2004-1406) affecting 3.1.0-3.1.3. 9. **The exact release date of Ikonboard 3.1.5** -- sources give 05/30/2006 and 06/02/2006. 10. **The Project Mongoose -> myIkonboard connection.** The 12/04/2003 capture proves myIkonboard's free tier ran PHP; that this PHP was Mongoose is reported but not confirmed from a primary source. 11. **Identities behind the handles KEVaholic00, Camil, Freakboy, and joshdw1.** Email addresses recovered from archived rosters (`kevaholic00@yahoo.com`, `ccollard@enter-net.com`) but no real names. Nurlan Mukhanov, Andrey Prokopenko, and Phil Gengler are named in the source itself. 12. **`ibskins.ikonboard.com`.** Described by the vendor **[ARCHIVE]** but the attempted capture retrieved an IIS 404 page. The site's actual contents are not documented here. --- ### 2.11 Summary What the code proves, on its own, without any external research: - Ikonboard 3.1.1 belongs to **Jarvis Entertainment Group, Inc.**, and 2.1.9 belonged to **Ikonboard.com**. The transfer was executed by automated bulk edits over the source tree, and those edits were incomplete (2 surviving Ikonboard.com copyrights, 88 header-less files) and unreviewed (`Mor2001-e`). - **Matthew Mecham wrote the architecture.** Eleven files carry his byline, ten of them the `iDatabase` layer, stamped **May 2001**. The installer's header says "Ikonboard by Matthew Mecham [ v3.0 ]" directly above "(c)2001 Jarvis Entertainment Group, Inc." - He was **still writing official Ikonboard documentation on 08/30/2001**, signed and dated, in the box. - The product was built by a **named volunteer community**: KEVaholic00, Camil, LrdChaos (Phil Gengler), Infection (Nurlan Mukhanov), Andrey Prokopenko, Freakboy, joshdw1 -- with inline `added by ` patch seams, one reference to a public bug tracker (`BUG FIX #168`), a shipped hack-writing SDK, and two modules that shipped with the SDK's placeholder boilerplate intact. - The **license moved from Gosport to Texas, from GBP to $, from UK law to US law**, added a "not open source" disclaimer, six enumerated modification conditions, assignment of contributor ownership, at-will termination, and a right to publish its users -- while keeping the software free. - The **business model is rendered in the admin sidebar** at `Menuadmin.pm:112-144`, and that block was the last thing anyone edited before the box shipped. - The **vulnerability that defined the product's reputation is at `Sources/Lib/FUNC.pm:191`**, three lines under a comment that says "Make sure the cookie data is legal," in a file that declares `use strict`, last touched two days before development stopped. What external research adds: the sale in April 2001, the founding of Invision Power Services in February 2002, and a disclosure timeline that runs eight months from private notification on 01/26/2003 to CVE-2003-0770 on 09/22/2003 -- during which the vendor shipped 3.1.2 on 05/13/2003 and 3.1.2a after it, **neither of them carrying the fix**, which was eventually written and published by the reporter. Then eight more years of ownership churn, ending in a brochure page that outlived its software by a decade. Ikonboard 3.1.1 is a 73,000-line Perl rewrite, built by a volunteer team for a Pink Sheets holding company, in the last year that Perl CGI forums were a viable product category, under a license that made its own cross-promotion links legally load-bearing, carrying a one-line remote code execution bug in its core library. Every one of those clauses is legible from the box. --- ## 3. Architecture Ikonboard 3.1.1 was released in July 2002 by Jarvis Entertainment Group, Inc. It is not an evolution of Ikonboard 2.1.9; it is a ground-up rewrite that kept the name and the URL scheme and almost nothing else. Seventeen source lines are shared between the two codebases -- 0.49% of 2.1.9, 0.11% of 3.1.1 -- and most of those seventeen are HTML comments and a date-formatting idiom. The file count goes from 43 Perl files to 179, the line count from 15,586 to 72,805, the subroutine count from 88 to 1,624, and `use strict` coverage from 0% to 74%. This chapter describes how the 3.1.1 machine actually runs a request: what the single CGI entry point does before it dispatches, how the dispatcher works, what sits underneath it (a five-driver database abstraction layer, a template compiler that emits Perl, a server-side session store, a language layer), and where the seams are. **A note on what is missing.** The reconstructed tree in `teardown/board/cgi-bin/` is a *distribution* tree -- the board was never installed. The single most important runtime file, `Data/Boardinfo.cgi`, does not exist here. It is *generated* by the installer, not shipped. Everything this chapter says about `$iB::INFO` is therefore reconstructed from three sources: the shipped defaults file `ikonboard.conf`, the generator `install_modules/functions.pm:433` (`write_boardinfo`), and the consumers scattered through `Sources/`. Where that reconstruction is uncertain, the text says so. --- ### 3.1 The shape #### 1.1 One script instead of forty Ikonboard 2.1.9 was a directory of independent CGI scripts. Thirty-one `.cgi` files sat in `cgi-bin/`, each one a complete program: `topic.cgi`, `post.cgi`, `search.cgi`, `admincenter.cgi`, `setforums.cgi`, and so on. Each began by pulling in a shared library and three data files: ```perl eval { ($0 =~ m,(.*)/[^/]+,) and unshift (@INC, "$1"); ($0 =~ m,(.*)\\[^\\]+,) and unshift (@INC, "$1"); require "ikon.lib"; # Require ikonboard () require "data/progs.cgi"; # Require prog names require "data/boardinfo.cgi";# Require board info require "data/styles.cgi"; # Require styles info }; ``` -- `ib219/cgi-bin/ikonboard.cgi:21-28` Every hyperlink on a 2.1.9 board pointed at a different executable. Every click started a fresh Perl interpreter, recompiled `ikon.lib` (the whole shared library, whether the script needed it or not), reread the flat config files, and exited. There was no database, no connection to keep, and no session: identity came straight off two cookies, in the clear, on line 45 of the entry script. ```perl $inmembername = cookie("amembernamecookie"); $inpassword = cookie("apasswordcookie"); ``` -- `ib219/cgi-bin/ikonboard.cgi:45-46` Neither `ikonboard.cgi` nor `ikon.lib` in 2.1.9 contains a single `use strict`. Ikonboard 3.1.1 collapses all of that into **one** executable -- `ikonboard.cgi`, 588 lines -- plus roughly 118 modules under `Sources/`. The script is a bootstrap and a dispatcher; it contains no forum logic at all. Every URL on a 3.x board is a query string against the same file: ``` ikonboard.cgi?act=ST;f=4;t=1201;st=15;s= ``` `act` selects the module, `CODE` selects the method inside it, `AD=1` (or `CP=1`) diverts the whole request into the admin control panel, and `s` carries the session ID for cookie-less clients. Semicolons rather than ampersands are the default separator (`$CGI::USE_PARAM_SEMICOLONS = 1` at `ikonboard.cgi:161`), with an escape hatch at the top of the file for old CGI.pm versions that could not cope (`ikonboard.cgi:41`, `153-159`). #### 1.2 A request, end to end ``` browser | | GET /cgi-bin/ikonboard.cgi?act=ST;f=4;t=1201;s= v +-----------------------------------------------------------------+ | Apache (mod_cgi, or mod_perl + Apache::Registry) | +-----------------------------------------------------------------+ | v +-----------------------------------------------------------------+ | ikonboard.cgi package iB | | | | 60 use lib ./Data ./Sources ./Skin ./Languages ./ | | 77 reset $iB::COOKIES_OUT SESSION IN TEMP_COOKIE | | COOKIES MEMBER ACTIVE <-- mod_perl hygiene | | 91 use constant IS_MODPERL; *iB::exit = Apache::exit | CORE | | 102 $SIG{__WARN__} filter (drops "uninitialized value") | | 109 $SIG{__DIE__} = \&catch_die | | 123 require "Boardinfo.cgi"; $iB::INFO = Boardinfo->new() | | 135 die if installer.cgi still present and no install.lock | | 147 use CGI; POST_MAX=500K; PRIVATE_TEMPFILES; HEADERS_ONCE | | 175 %iB::IN = map { _clean_key => _clean_value } CGI params | | 180 $iB::IN{AD} ||= $iB::IN{CP} | | 186 read cookies whose name starts with COOKIE_ID | | 211 ARC4 key bootstrap -> decrypt DB_PASS | | 289 $db = iDatabase::SQL->new( DB_DRIVER => ... ) | | 307 $std = FUNC::STD->new(); $sess = Sessions->new() | | 317 IP_ADDRESS from HTTP_X_FORWARDED_FOR || REMOTE_ADDR | | 335 $std->ValidateEntry($db) referer + /proc/loadavg | | 338 $iB::SKIN = $std->LoadSkin() -> Skin/

/Styles.pm | | 340 do Skin//Universal.pm | | 352 $iB::MEMBER = $sess->authenticate($db) | | 353 $iB::ACTIVE = $sess->active_users($db) (conditional) | | 376 eval { iB::Action($db) } || $std->cgi_error($@) | +-----------------------------------------------------------------+ | v +-----------------------------------------------------------------+ | iB::Action | | 396 AD or CP -> Admin::Functions->process($db) [stage 3] | | 404 board offline? 413 force login? | | 424 %Mode : 44 act= keys -> [ module, method ] | | 473 unknown act is forced to BoardIdx <-- the guard | | 485 build "require M; my $idx = M->new(); $idx->meth($db);" | | 489 eval $code <-- stage 1 | +-----------------------------------------------------------------+ | v +-----------------------------------------------------------------+ | Sources/Topic.pm (or 43 others) | | - BEGIN { require 'Lib/FUNC.pm' } | | - $Topic::lang = $std->LoadLanguage('TopicWords') | | - require $iB::SKIN->{DIR} . '/TopicView.pm' | | - per-module %Mode on $iB::IN{CODE} <-- stage 2 | | - $db->query( TABLE => ..., WHERE => ... ) | +-----------------------------------------------------------------+ | | v v +---------------------------+ +------------------------------+ | iDatabase::SQL | | Skin//TopicView.pm | | AUTOLOAD -> Driver | | sub RenderRow { | | DBM|mySQL|pgSQL|Oracle | | return qq~ ... ~| | Driver/Base.pm defaults | | } | +---------------------------+ +------------------------------+ | | +-----------------+-----------------+ v +-----------------------------------------------------------------+ | FUNC::Output::print_ikonboard (Lib/FUNC.pm:794) | | - board template row from the `templates` table | | - substitute <% TITLE %> <% IKONBOARD %> <% NAVIGATION %> ... | | - optional SSI expansion, optional HTML compressor | | - _print_http_header (emits @{$iB::COOKIES_OUT}) | | - print $it; iB::exit() | +-----------------------------------------------------------------+ | v HTML to browser ``` #### 1.3 Where the code lives | subsystem | files | Perl lines | |---|---:|---:| | Admin control panel (`Sources/Admin/`) | 34 | 24,088 | | Front-end controllers (`Sources/*.pm`) | 33 | 16,629 | | Skin (compiled views + templates) | 61 | 6,566 | | Database abstraction layer (`Sources/iDatabase/`) | 13 | 6,530 | | Installer | 9 | 4,304 | | Core library (`Sources/Lib/`) | 5 | 2,906 | | User CP + messenger | 6 | 2,528 | | Language packs | 33 | 2,344 | | Search subsystem | 6 | 1,683 | | Small feature modules (`Sources/Misc/`) | 12 | 1,323 | | Bundled CPAN (Archive::Tar, Compress::Zlib, MIME::*) | 5 | 1,949 | | mod_perl glue | 1 | 82 | (from `out_manifest.txt` section 3.1) A third of the Perl in Ikonboard 3.1.1 is the administration control panel. `Sources/Admin/Options.pm` alone is 2,298 lines -- larger than any front-end module, larger than any database driver. The board's user-facing feature set is smaller than its configuration surface. --- ### 3.2 Startup sequence What follows walks `ikonboard.cgi` top to bottom. Nothing here is forum logic; it is all environment construction. Roughly 370 lines run before a single line of `Sources/` does anything user-visible. #### 2.1 Package and library path (lines 2, 60-68) ```perl package iB; $| = 1; use strict; ``` The entry script declares `package iB` and everything the board treats as global state lives in that package: `$iB::INFO`, `%iB::IN`, `$iB::MEMBER`, `$iB::SKIN`, `$iB::SESSION`, `$iB::COOKIES`, `$iB::CGI`, `$iB::VERSION`. Modules reach into `iB::` directly rather than being passed a context object. This is the single most consequential design decision in the codebase: it makes every module implicitly dependent on the entry script having run, and it is why the mod_perl resets in the next section have to exist. ```perl use lib ( './Data' , './Sources', './Skin' , './Languages', './', ); ``` -- `ikonboard.cgi:60-65` The comment above it explains why `use lib` rather than `unshift @INC`: > mod_perl doesn't seem to like manual unshifting of the @INC because it *has* to > go in a BEGIN {} block as mod_perl only see's this once, things get out of > whack. "use lib" is a more elegant solution. > -- `ikonboard.cgi:45-50` The paths are *relative*. That works under mod_cgi because Apache chdirs to the script's directory; the comment at lines 51-58 tells the admin to hand-edit absolute paths in if it does not. `use Benchmark` at line 68 is loaded unconditionally, only to produce the execution-time line in the admin stats box (`Lib/FUNC.pm:852-857`). #### 2.2 mod_perl global resets (lines 77-93) ```perl $iB::COOKIES_OUT = []; $iB::SESSION = undef; %iB::IN = (); @iB::TEMP_COOKIE = (); $iB::COOKIES = {}; $iB::MEMBER = undef; $iB::ACTIVE = undef; ``` -- `ikonboard.cgi:77-83` The comment above is candid about why: > Even though we use strict, $iB is a global package and as mod_perl compiles > once and runs, all the values are carried over. We don't want that. > -- `ikonboard.cgi:73-75` This is correct, and it is the right fix for the seven variables named. Section 8 covers the state it does *not* reach. ```perl use constant IS_MODPERL => $ENV{MOD_PERL}; use subs qw(exit); *iB::exit = IS_MODPERL ? \&Apache::exit : sub { CORE::exit }; ``` -- `ikonboard.cgi:91-93` Calling `exit()` inside a mod_perl child kills the Apache process. The board therefore installs its own `iB::exit`, resolved once at compile time, that maps to `Apache::exit` under mod_perl and `CORE::exit` otherwise. Every terminating path in the codebase -- `catch_die` (line 575), `print_ikonboard` (`Lib/FUNC.pm:983`), `FUNC::STD::Error` (`Lib/FUNC.pm:664`), `Sessions::do_log_in` (`Sources/Sessions.pm:498`) -- calls `iB::exit()` rather than `exit`. That discipline is unusually consistent for the era. #### 2.3 The warning filter (lines 102-107) ```perl $SIG{__WARN__} = sub { my $wn = shift; return if $wn =~ /Use of uninitialized value/i; #Most annoying return if $wn =~ /name "(?:.+?)" used only once/i; #Very annoying warn $wn; }; ``` Two whole classes of Perl diagnostic are discarded before they reach the error log. Say plainly what that costs. "Use of uninitialized value" is the warning Perl emits when you interpolate or compare a variable that was never given a value. In a codebase where identifiers are fully-qualified package globals -- which `use strict 'vars'` permits without declaration -- that warning is the *only* signal that a name is wrong. `use strict` cannot catch `$iB::PTH` because `$iB::PTH` is syntactically a legal package variable; it catches `$pth` but not `$iB::pth`. The tree contains a live example. `Sources/Boards.pm:65`: ```perl unless (-e $iB::PTH.'/Data/ForumJump.pm') { $std->build_forumjump( DB => $db, CATS => $obj->{'TOTAL_CATS'}, FORUMS => $obj->{'TOTAL_FORUMS'} ); } ``` `$iB::PTH` is assigned nowhere in the distribution -- a full-tree search finds exactly one occurrence, this one. The test therefore reduces to `-e '/Data/ForumJump.pm'`, an absolute path from filesystem root that will not exist on any normal host. So the guard never fires, and `build_forumjump` (`Lib/FUNC.pm:256`) runs on **every** board-index request: it walks all categories and all forums, then calls `FUNC::ADMIN::make_module` (`Lib/ADMIN.pm:210`), which copies the existing `Data/ForumJump.pm` to a backup and writes a fresh one. Two file writes and a full category/forum traversal per front-page hit, forever, because one warning class was silenced. The second suppressed class, `"name used only once"`, hides exactly the same category of typo in the other direction. Line 109 installs `$SIG{__DIE__} = \&catch_die`, which turns any `die` anywhere in the tree into a styled HTML error page with `$ENV{DOCUMENT_ROOT}` scrubbed out of the message (`ikonboard.cgi:546-576`). Line 116 shows a commented-out `$SIG{ALRM}` watchdog that would append to `Data/timeout_log` for any request over 30 seconds; the matching `alarm(30)` / `alarm(0)` calls are commented out at lines 387 and 493. It shipped disabled. #### 2.4 Configuration (lines 123-140) ```perl require "Boardinfo.cgi"; $iB::INFO = Boardinfo->new(); ``` -- `ikonboard.cgi:123-124` `Boardinfo.cgi` is resolved through the `./Data` entry in `@INC`; the real path is `cgi-bin/Data/Boardinfo.cgi`. **It is not in this tree**, because it does not exist until the installer writes it. Its generator is `install_modules/functions.pm:433` (`write_boardinfo`), which emits a trivial package with a single constructor: ```perl print FH <<_END_PRINT_; package Boardinfo; sub new { my \$pkg = shift; my \$obj = { _END_PRINT_ for my $key (sort { $a cmp $b } keys %{$data}) { my $space = " " x (20 - (length($key))); $data->{$key} =~ s|!|!|g; print FH qq~'$key' $space => q!$data->{ $key }!,\n~; } ``` -- `install_modules/functions.pm:457-470` Note `q!...!` -- single-quoted, non-interpolating, with `!` escaped to `!` on the way in. Configuration values are inert strings. (The near-identical `FUNC::ADMIN::make_module` at `Lib/ADMIN.pm:210` takes an `INTERPOLATE` flag and will use `qq!...!` instead -- that is how the admin CP rewrites `Boardinfo.cgi` in place when settings change.) The key set is recoverable from the shipped defaults file, `cgi-bin/ikonboard.conf`, 118 lines of `KEY = value`: ``` BOARD_URL = CGI_EXT = COOKIE_ID = DB_DIR = DB_DRIVER = DB_NAME = DB_PASS = FLOCK = 1 GUEST_GROUP = 2 IKON_DIR = SESSION_EXPIRATION = 3000 SKINS = 1:Default:Standard Ikonboard Skin SUPAD_GROUP = 4 ``` Blank values are the ones the installer fills in. Compound values are packed into single strings with `|&|` as the record separator and `:` as the field separator -- `SKINS`, `FORUM_SKINS`, `EMOTICONS`, `IP_FILTER`, `EMAIL_FILTER`, `LANGUAGES`, `WORD_FILTER`, `SKIN_TEMPLATES` all use this convention, and every consumer re-splits it by hand at the point of use (e.g. `Sources/Sessions.pm:104`, `Lib/FUNC.pm:139`). Lines 126-129 patch one omission at runtime: if `PUBLIC_UPLOAD` is empty, derive it from `HTML_DIR` by substituting `non-cgi` for `uploads`. Lines 135-140 are the installer guard: ```perl if ( (-e $iB::INFO->{'IKON_DIR'}."installer.cgi") && (!(-e $iB::INFO->{'IKON_DIR'}."install.lock")) ) { &catch_die("FATAL ERROR:
The installer (installer.cgi) is still present in the root ikonboard ". "directory. Ikonboard will not run until this file is removed!
". ``` The board refuses to run at all if `installer.cgi` is present *and* `install.lock` is absent. `install.lock` is written by `install_modules/admin.pl:162` at the end of a successful install, and `installer.cgi:117` refuses to re-run once it exists. The two files interlock: a completed install leaves a lock so the board runs; a half-finished one leaves the installer exposed and the board refuses to start rather than let an unauthenticated visitor reach it. That is a better failure mode than most contemporaries managed. #### 2.5 CGI.pm and the input filter (lines 147-192) ```perl $CGI::USE_PARAM_SEMICOLONS = 1; $CGI::PRIVATE_TEMPFILES = 1; $CGI::HEADERS_ONCE = 1; $CGI::POST_MAX = 500*1024; ``` -- `ikonboard.cgi:161-164` 500 KB post limit (2.1.9 used 150 KB and disabled uploads outright -- `ib219/cgi-bin/ikonboard.cgi:18-19`). `PRIVATE_TEMPFILES` makes CGI.pm unlink upload temp files immediately after opening them so other users on a shared host cannot read them; that is a deliberate shared-hosting hardening choice, and the commented-out `$TempFile::TMPDIRECTORY` at line 169 is its escape hatch. A comment at 143-145 notes that CGI.pm is a stopgap: "We'll be using CGI.pm until iCGI.pm is mod_perl compatible." No `iCGI.pm` ships. Then the whole parameter space is harvested and filtered in one statement: ```perl %iB::IN = map { &iB::_clean_key($_) => &iB::_clean_value($iB::CGI->param($_)) } $iB::CGI->param; ``` -- `ikonboard.cgi:175` ```perl sub _clean_key { my $key = shift; return '' unless defined $key; $key =~ s!\.\.!!g; $key =~ s!\_\_(.+?)\_\_!!g; &iB::_trim($key); $key =~ m!^([\w\.-\_]+)$!; return $1; } ``` -- `ikonboard.cgi:506-514` ```perl sub _clean_value { my $Tmp = shift; return '' unless defined $Tmp; $Tmp =~ s|&|&|g; $Tmp =~ s||-->|g; $Tmp =~ s||>|g; $Tmp =~ s|<|<|g; $Tmp =~ s|"|"|g; ... $Tmp =~ s|\$|$|g; ... } ``` -- `ikonboard.cgi:516-537` Three architectural observations, without straying into the security chapter: 1. **This is an HTML filter, not a general one.** It escapes the characters that matter in markup. It does not quote for SQL, does not normalize paths beyond deleting literal `..`, and does not quote for the shell. Every later layer must assume input is HTML-safe and nothing else. 2. **`$` is escaped to `$` for a structural reason.** Skin templates are compiled into `qq~...~` strings (section 3.5). Any `$` that survived into a template body would be interpolated as a Perl variable when the view ran. The escape at line 530 is what makes the skin engine safe to feed user data. The mirror-image un-escape lives in `FUNC::STD::doHTML` (`Lib/FUNC.pm:438-447`). 3. **`map` over `param()` takes only the first value of a multi-valued parameter**, because `$iB::CGI->param($_)` is called in scalar context. Any code that genuinely needs a list -- the skin editor's `SUB:` fields, the session ID -- goes back to `$iB::CGI->param()` directly (`Sources/Sessions.pm:96`, `Admin/SkinControl.pm:1134`). `%iB::IN` is a flattened, sanitized view; `$iB::CGI` is the raw one, and both are in scope everywhere. Line 180 is the AD/CP alias, discussed in section 3.3.4: ```perl $iB::IN{AD} ||= $iB::IN{CP}; ``` Then cookies, filtered by prefix so the board never touches anything that is not its own: ```perl @iB::TEMP_COOKIE = $iB::CGI->cookie(); for my $c (@iB::TEMP_COOKIE) { # Only get 'our' cookies next unless $c =~ /^$iB::INFO->{'COOKIE_ID'}/; # Add it to our hash $iB::COOKIES->{$c} = $iB::CGI->cookie($c); } ``` -- `ikonboard.cgi:186-192` `COOKIE_ID` is a per-board prefix set at install time. It is what lets two Ikonboards share a hostname without trampling each other's cookies -- the same role `DB_PREFIX` plays for SQL tables. #### 2.6 The ARC4 database-password bootstrap (lines 211-267) This block is the strangest part of startup and deserves a careful reading. ```perl require 'Sources/ARC4.pm' or die "Cannot open ARC4"; require 'Sources/MIME/Base64.pm' or die "Cannot open Base64"; opendir (DIR, $iB::INFO->{'IKON_DIR'}.'Data'); my @list = grep { !/\A\.{1,2}\Z/ } readdir(DIR); closedir(DIR); my @key = grep { /.+?(\.pwd)\Z/ } @list; ``` -- `ikonboard.cgi:211-216` The board scans `Data/` for a file ending in `.pwd`. That filename *is* the encryption key. If no such file exists, the board is running for the first time since the feature was added, and it performs a one-time migration: ```perl unless (scalar @key > 0) { my $file = &iB::my_gen_key(); my $file_name = $iB::INFO->{'IKON_DIR'}.'/Data/' . $file . '.pwd'; open (KEYF, ">" . $file_name ) or die "Cannot write to $file_name ($!)"; print KEYF $file_name; close KEYF; chmod ( 0644, $file_name ); $file = $file . '.pwd'; my $ark4 = Crypt::ARC4->new($file); #preparing the crypting module my $OLD = Boardinfo->new(); if ($iB::INFO->{'DB_PASS'}) { $OLD->{'DB_PASS'} = MIME::Base64::encode_base64($ark4->ARC4( $iB::INFO->{'DB_PASS'} )); # crypting the password $OLD->{'DB_PASS'} =~ s!\n\Z!!; } ``` -- `ikonboard.cgi:217-230` It generates a 16-character random name, creates `Data/.pwd`, then uses that *name* (not the file's contents) as the ARC4 key to encrypt each of the five possible database passwords (`DB_PASS`, `mySQL_DB_PASS`, `pgSQL_DB_PASS`, `Oracle_DB_PASS`, `DBM_DB_PASS`), Base64-encodes them, and rewrites `Boardinfo.cgi` through `FUNC::ADMIN::make_module` at lines 255-260. On every subsequent request the reverse runs: ```perl unless ($iB::INFO->{'DB_DRIVER'} eq 'DBM') { for my $f (@key) { my $ark4 = Crypt::ARC4->new($f); $iB::INFO->{'DB_PASS'} = $ark4->ARC4( MIME::Base64::decode_base64($iB::INFO->{'DB_PASS'}) );# decrypting the pass } } ``` -- `ikonboard.cgi:262-267` Architecturally, this is a *filename-as-key* scheme layered on a config file that the web server can already read. Its stated purpose is to stop a plaintext database password appearing in `Boardinfo.cgi` -- a real concern in 2002, when a misconfigured server would happily serve `.cgi` files as text. Its practical effect is that anyone who can list `Data/` can decrypt. The DBM driver skips decryption entirely, because DBM has no password. Two incidental notes. The key generator seeds from a subprocess: ```perl srand (time ^ $$ ^ unpack "%L*", `ps axww | gzip`); ``` -- `ikonboard.cgi:278` which forks `ps` and `gzip` on every first-run -- and on any platform without them (Windows), silently contributes nothing. And lines 251-253: ```perl if ($^O eq 'MacOS' && ($^O eq 'MSWin32' || !Win32::IsWin95())) { $OLD->{'FLOCK'} = 0; } ``` `$^O` cannot be both `MacOS` and `MSWin32`; the condition is unsatisfiable, so `FLOCK` is never turned off here. The intended test is almost certainly the `&&` of an `ne`-style guard used correctly elsewhere in the tree (`iDatabase/Driver/DBM.pm:24`, `iDatabase/Driver/CSV.pm:21`). Flagging as a bug, not a design. #### 2.7 The database handle (lines 286-303) ```perl my $create = $iB::INFO->{DB_DRIVER} eq 'DBM' ? 1 : 0; my $drop = $iB::INFO->{DB_DRIVER} eq 'DBM' ? 1 : 0; my $db = iDatabase::SQL->new( DATABASE => $iB::INFO->{'DB_NAME'}, DB_DIR => $iB::INFO->{'DB_DIR'}, IP => $iB::INFO->{'DB_IP'}, PORT => $iB::INFO->{'DB_PORT'}, USERNAME => $iB::INFO->{'DB_USER'}, PASSWORD => $iB::INFO->{'DB_PASS'}, DB_PREFIX => $iB::INFO->{'DB_PREFIX'}, DB_DRIVER => $iB::INFO->{'DB_DRIVER'}, ATTR => { allow_create => $create, allow_drop => $drop, }, ); # Check for errors &catch_die( $db->error ) if $db->error; ``` `allow_create` and `allow_drop` are true **only** for DBM. That is deliberate: DBM "tables" are files that appear on demand, so the driver must be allowed to make them. For a real SQL server the schema is installed once by the installer and the runtime handle is denied DDL. `$db` is then threaded as an explicit argument to every method in the board -- `$idx->ShowTopic($db)`, `$sess->authenticate($db)`, `$ad->process($db)`. It is the one piece of state the codebase passes around rather than reaching for in `iB::`. #### 2.8 Identity, skin, and the last of the setup (lines 307-373) ```perl my $std = FUNC::STD->new(); my $sess = Sessions->new(); ``` `$iB::TT0 = new Benchmark;` (line 312) starts the clock. Lines 317-324 resolve the client IP, preferring `HTTP_X_FORWARDED_FOR` and taking only the first address if a proxy chain supplied several -- a 2002-appropriate choice that also means the IP the board binds a session to is attacker-controllable via a header. ```perl $std->ValidateEntry($db); ``` -- `ikonboard.cgi:335` `ValidateEntry` (`Lib/FUNC.pm:395-423`) does two unrelated things. First, a referer check -- but only for `POST` requests where `act` is exactly `Post`: ```perl if (lc($ENV{'REQUEST_METHOD'}) eq 'post' and $ENV{'HTTP_REFERER'} and $iB::IN{act} eq 'Post') { my $b_url = $INFO->{'BOARD_URL'}; $b_url =~ s!http://!!i; $obj->Error(DB => $db, STD => $obj, LEVEL=>'5',MESSAGE=>'referrer_fail') unless $ENV{'HTTP_REFERER'} =~ m!$b_url!i; } ``` Second, if `/proc/loadavg` is readable, it reads the one-minute load average into `$iB::CONTENT->{'LOAD'}` and, if `LOAD_LIMIT` is configured and exceeded, aborts the request with a "server too busy" page. A CGI forum that sheds load by reading `/proc` is a genuinely thoughtful bit of shared-hosting engineering. ```perl $iB::SKIN = $std->LoadSkin(); do $iB::SKIN->{'DIR'} . '/Universal.pm'; ``` -- `ikonboard.cgi:338-340` `LoadSkin` (`Lib/FUNC.pm:99-177`) resolves the skin by a three-rule cascade: admin CP always gets the default skin; a per-forum skin from `FORUM_SKINS` overrides; otherwise an `sid` URL parameter or a `skin` cookie selects from the `SKINS` string. It `require`s `Skin//Styles.pm` and returns `Styles::new("Styles")` -- a hash of colors and image tags -- decorated with `DIR`, `FULL_DIR`, and `IMAGES_URL`. Note `do` rather than `require` for `Universal.pm`: `do` re-executes on every call, so the correct skin's universal elements always win. `Lib/FUNC.pm:23` has already hard-`require`d `'Default/Universal.pm'` at compile time regardless of skin; line 340 overwrites those subs with the chosen skin's. It works, but it means package `Universal` is whatever was loaded last. Lines 344-346 carry a marked bug fix in the source: ```perl # ( ADDED HERE BY KEVaholic00 FOR BUG FIX #168, COMMENTED OUT BELOW ) # Lets add on the skin name for ease of use. my $images_url = $iB::INFO->{'IMAGES_URL'}; $iB::INFO->{'IMAGES_URL'} .= '/' . $iB::SKIN->{'FULL_DIR'}; ``` with the superseded version left in place, commented, at lines 366-370. The tree carries several of these attributed in-line patches (`KEVaholic00`, `Camil`, `Infection`, `Andrey Prokopenko`, `Nurlan`) -- evidence of a codebase being maintained by a rotating volunteer team rather than a single author. ```perl $iB::MEMBER = $sess->authenticate($db) unless $iB::IN{'act'} eq 'Reg'; $iB::ACTIVE = $sess->active_users($db) if (( ($iB::IN{'act'} eq 'st' || $iB::IN{'act'} eq 'ST' || $iB::IN{'act'} eq 'Profile') || !$iB::IN{'act'}) or (defined $iB::IN{'c'} )); ``` -- `ikonboard.cgi:352-353` Authentication runs for every action except registration. The active-users scan -- a full `WHERE RUNNING_TIME > (time-900)` sweep of `active_sessions` -- runs only on the board index, topic views, profile views, and category views, because it is expensive. Note `'st'` and `'ST'` are both tested but `%Mode` only has `ST`; the lowercase branch is dead as far as dispatch is concerned but still triggers the scan. Finally: ```perl eval { iB::Action($db) } || $std->cgi_error($@); iB::exit(); ``` -- `ikonboard.cgi:376-377` The entire request body runs inside a block `eval`. Any `die` -- including the ones raised by the drivers on SQL failure -- lands in `cgi_error`, which delegates to `catch_die` and prints the styled error page. --- ### 3.3 The dispatcher `iB::Action` (`ikonboard.cgi:382-502`) is three dispatch stages stacked, plus two gates. #### 3.1 The gates ```perl if ($iB::IN{'AD'} or $iB::IN{CP}) { require Admin::Functions; my $ad = Admin::Functions->new(); $ad->process($db); return "0 but true"; } ``` -- `ikonboard.cgi:396-401` Admin wins before anything else -- including before the board-offline check, so an administrator can reach the CP while the board is down. Then: ```perl unless ($iB::INFO->{'B_ONLINE'}) { unless ($iB::MEMBER_GROUP->{'ACCESS_OFFLINE'}) { ``` -- `ikonboard.cgi:404-405` and the forced-login gate at 413-421, which exempts only `Reg` and `LostPass`. #### 3.2 Stage 1 -- the `%Mode` table ```perl my %Mode = ( ST => ['Topic' , 'ShowTopic' ], SF => ['Forum' , 'ShowForum' ], SR => ['Forum' , 'ShowRules' ], SC => ['Boards' , 'ShowStart' ], Search => ['Search::api' , 'Process' ], Online => ['Online' , 'Process' ], Legends => ['Legends' , 'Process' ], Help => ['Help' , 'Process' ], Members => ['Memberlist' , 'Process' ], Reg => ['Register' , 'Process' ], Post => ['Post' , 'Process' ], Login => ['LogInOut' , 'Process' ], Profile => ['Profile' , 'Process' ], UserCP => ['UserCP::Menu' , 'Process' ], Mod => ['Moderate' , 'Process' ], Poll => ['iPoll' , 'Process' ], Print => ['PrintPage' , 'Process' ], Invite => ['Misc::Invite' , 'Process' ], Mail => ['Misc::MailMember' , 'Process' ], Cookies => ['Misc::Cookies' , 'Process' ], PMarkers => ['Misc::PMarkers' , 'Process' ], Forward => ['Misc::Forward' , 'Process' ], AOL => ['Misc::AOL' , 'Process' ], ICQ => ['Misc::ICQ' , 'Process' ], MSN => ['Misc::MSN' , 'Process' ], Attach => ['Misc::Attachments' , 'Process' ], Msg => ['UserCP::Messenger' , 'Process' ], MSV => ['UserCP::Messview' , 'Process' ], MSS => ['UserCP::Messsend' , 'Process' ], MSM => ['Massmsend' , 'Process' ], Subs => ['Misc::Track' , 'Process' ], LostPass => ['UserCP::Lostpass' , 'Process' ], BoardIdx => ['Boards' , 'ShowStart' ], ModCP => ['ModCP' , 'Process' ], Calendar => ['Calendar' , 'Process' ], Report => ['Misc::Report' , 'Process' ], Upgrade => ['Upgrade' , 'Process' ], Warn => ['Warn' , 'Process' ], # Added by KEVaholic00: member notepads NotePad => ['NotePad' , 'Process' ], # Added by Camil: Newest post NW => ['Newest' , 'shownewest' ], ModSet => ['ModSet' , 'Process' ], Welcome => ['Welcome' , 'Process' ], Posters => ['Posters' , 'showposter' ], Happybd => ['Happybd' , 'Process' ], ); ``` -- `ikonboard.cgi:424-470` Forty-four actions. The table is rebuilt on every request -- it is a lexical inside `Action`, not a package constant -- which costs 44 hash-store operations per hit. Trivial under mod_cgi, pure waste under mod_perl, and a symptom of the whole file being written for the CGI case first. The convention is: value `[0]` is the module (`require`-able name, `::` included), value `[1]` is the method. Almost every module standardizes on `Process`; the exceptions are the six oldest and largest views (`Topic`, `Forum`, `Boards`) and three later additions (`Newest`, `Posters`) that never got normalized. `SC` and `BoardIdx` both point at `Boards::ShowStart`, `SF` and `SR` both at `Forum`, so 44 keys resolve to 39 distinct module/method pairs. The mix of two-letter opaque codes (`ST`, `SF`, `SC`, `NW`, `MSV`, `MSS`, `MSM`) and readable words (`Search`, `Profile`, `Calendar`) is chronological: the abbreviations are the original 3.0 set, kept because they were already in bookmarks and links, and every later feature spelled its action out. #### 3.3 The guard, and the eval ```perl $iB::IN{'act'} = 'BoardIdx' if $iB::IN{'act'} eq ''; $iB::IN{'act'} = 'BoardIdx' unless exists $Mode{ $iB::IN{'act'} }; # Nice little hack to save writing loads of subroutines for each action. # It builds the code from the %Mode hash, depending on the contents of 'act' # For example, the eval'd code may look like: # use Topic; # my $idx = Topic->new(); # $idx->ShowTopic($db); my $code = 'require '.$Mode{ $iB::IN{'act'} }[0].';'. 'my $idx = '.$Mode{ $iB::IN{'act'} }[0].'->new();'. '$idx->' .$Mode{ $iB::IN{'act'} }[1].'($db);'; eval $code; ``` -- `ikonboard.cgi:473-489` A string `eval` whose content is built from a query parameter looks like remote code execution. **It is not, and the check is one line above it.** Line 474 rewrites `act` to `BoardIdx` unless it is already an existing key of `%Mode`. By the time `$code` is assembled, `$iB::IN{'act'}` can only be one of the 44 literals in the table, so the only strings that can ever be interpolated are the 88 literals in the table's values. Nothing attacker-supplied reaches the eval. State that plainly; the alternative is an archive note that misleads every future reader. What the eval buys is laziness. `require` inside an eval'd string means the module for the requested action -- and only that module -- is compiled. A board index request never parses `ModCP.pm` (2,131 lines) or `Post.pm` (1,623). Under mod_cgi that is the difference between a 100 ms page and a 500 ms one. It is the same reason the whole thing is one script: the dispatcher pays for exactly the code the request needs, which the forty-script model could not do because each script had to `require ikon.lib` whole. The closing comment is worth preserving verbatim as period voice: ```perl return "shut up complaining mod_perl or I'll kick your ass"; ``` -- `ikonboard.cgi:499` with the explanation above it at 495-497: because `Action` is wrapped in `eval { } || $std->cgi_error($@)`, a false return would trigger the error handler with an empty `$@`. #### 3.4 The AD -> CP aliasing Line 180 does `$iB::IN{AD} ||= $iB::IN{CP};` with no comment. The explanation is 216 lines later, inside `Action`: ```perl # As the admin link has "AD=1" in it, some firewalls/banner blockers # will produce a blank page, not what we want. # As Ikonboard 3 has used AD=1 since day 1, I don't want to have to weed # through the code looking for every single instance it's been used, so # we merely use perls' excellent reg-ex to turn AD into CP. For those who # have bookmarked their adminCP link, we allow AD=1 to be used also. ``` -- `ikonboard.cgi:389-394` Ad-blocking software in 2002 pattern-matched URLs for `AD=`, and ate the administrator's control panel. Rather than rename the parameter across a 34-module admin subsystem, the author accepted a new name at the front door and aliased it to the old one before anything else ran. Every `$iB::IN{'AD'}` test in `Sources/` -- `Lib/FUNC.pm:113` in `LoadSkin`, `Sources/Sessions.pm:213` and `:218` in `authenticate` -- continues to work unmodified. The comment's claim about "perls' excellent reg-ex" does not match the code, which is a plain `||=`; it is probably a leftover from a first attempt. #### 3.5 Stage 2 -- per-module `CODE=` Once a module is running, it dispatches again on `$iB::IN{'CODE'}`. The shipped template for third-party developers documents the pattern: ```perl sub Process { my ($obj, $db) = @_; my $CodeNo = $iB::IN{'CODE'}; # We set up a hash. The left hand side is what the CODE value # was from the accessing URL (eg: CODE=save_date). To the right # is the sub routine that it will execute. ... my %Mode = ( 'printable_date' => \&printable_date, 'another_sub' => \&sub_two, ); $Mode{$CodeNo} ? $Mode{$CodeNo}->($obj,$db) : FatalError(); } ``` -- `ib311/Tools/writing_hacks/module_template.pm` Stage 2 uses **code references**, not string eval -- the module is already compiled, so there is nothing to defer. Across the tree there are 152 second-stage endpoints in 27 modules (`out_actions.txt` section 3.2). The largest are `ModCP` (22 codes), `Moderate` (20), `UserCP::Messenger` (12), `Profile` (9), `Post` (8), `NotePad` (7). `CODE` values are as inconsistent as `act` values, and for the same reason: zero-padded numerics (`CODE=00` ... `CODE=19`) in the modules inherited from 3.0, readable words (`CODE=topic_search`, `CODE=do_forum_rules`, `CODE=process_move`) in everything written later. `Post.pm` and `iPoll.pm` do it *both* ways -- a `%Mode` hash and an `if/elsif` chain on the same `CODE` values. The static analysis also finds seven `CODE` entries pointing at handlers that do not exist in the module that names them -- `ModSet` `edit`, `Moderate` `10` and `11`, `Online` `forum`, `Register` `06`, `UserCP::Messsend` `13`, `iPoll` `03` (`out_actions.txt` section 3.2). Some are inherited into the package from elsewhere; some are simply dangling. Because the second-stage lookup uses `\&name`, a missing sub is a compile-time-resolvable stub that dies at call time, not a silent no-op. #### 3.6 Stage 3 -- the admin control panel `Admin::Functions::process` (`Sources/Admin/Functions.pm:46`) is the third dispatcher, and it is guarded by three sequential checks before it reaches its table: 1. **Logged in at all** -- `unless ($iB::MEMBER->{'MEMBER_ID'})`, print the CP login form (line 51). 2. **Group permission** -- `unless ($iB::MEMBER_GROUP->{'ACCESS_CP'})`, append the attempt to `Temp/log-.cgi`, set the member's `ALLOW_POST` to 0, and error out (lines 60-76). Trying to reach the CP without permission costs you your posting rights. 3. **A live admin session** -- a separate timestamp file `Temp/admin-.cgi` must exist and be newer than 8 hours (`60*480` seconds, line 82); otherwise the file is unlinked and the CP login form is shown (lines 77-102). The admin session is thus a *second* authentication layer with its own storage (a file, not the `active_sessions` table) and its own expiry, entered through `act=dologin` (line 152) and torn down on timeout. Only then: ```perl my %Mode = ( dologin => \&dologin, top => \&top, body => \&body, cat => \&cat, ops => \&Ops, bak => \&back, ... menuadmin => \&menuadmin, ); $Mode{ $iB::IN{'act'} } ? $Mode{ $iB::IN{'act'} }->($obj, $db) : Frames($obj, $db); ``` -- `Sources/Admin/Functions.pm:104-140` Thirty-three entries, each a thin loader: ```perl sub forum { my ($obj, $db) = @_; require 'Admin/ForumControl.pm'; my $idx = Admin::ForumControl->new()->process($db); } ``` -- `Sources/Admin/Functions.pm:327-331` Same lazy-require economy as stage 1, expressed as code references instead of eval because the names are fixed. The fallback is `Frames` (line 361), which emits the frameset -- the CP is a three-frame HTML application, and `top` and `body` are the frame sources. Note that stage 3 **reuses `act`**. The same query parameter names a front-end module when `AD` is absent and an admin section when it is present. `%Mode` in `ikonboard.cgi` and `%Mode` in `Admin::Functions` are disjoint namespaces selected by a third parameter. It works, but it means `act=forum` means nothing on the front end and `act=ST` means nothing in the CP, and neither table documents the other's existence. --- ### 3.4 The database abstraction layer This is the headline architectural change from 2.x. Ikonboard 2.1.9 had no database: `data/allforums.cgi`, `members/.cgi`, one file per forum of pipe-delimited posts. Ikonboard 3.1.1 has `Sources/iDatabase/` -- 13 files, 6,530 lines -- sitting between every module and storage. #### 4.1 `iDatabase::SQL` -- the factory and forwarder ```perl # Attempt to load the correct driver my $class = "iDatabase::Driver::$args{DB_DRIVER}"; # Make global $DRIVER = $class; { local ($@); my $r_class = $class; $r_class =~ s~::~/~g; eval { require "$r_class.pm" }; if ($@) { # Until we do something more elaborate die "Cannot load driver: $args{DB_DRIVER} (Perl says: $@)"; return; } } # Get a new SQL connection $obj->{driver} = $class->newSQL( \%args ); ``` -- `Sources/iDatabase/SQL.pm:44-62` The `$db` object every module receives is a thin wrapper holding `{driver}`, `{database}`, `{name}`, `{base_dir}`, `{prefix}`. It defines almost no methods itself. Everything else goes through `AUTOLOAD`: ```perl sub AUTOLOAD { my $obj = $_[0]; # Don't shift # Return if it's a destroy method return if $AUTOLOAD =~ /::DESTROY$/; # Get the method and package name my ($pkg, $method) = $AUTOLOAD =~ /^(.*)::([^:]+)$/; # If it's a sub to be compiled here, then do so, if not # pass it on to the driver module if (exists $SUBS{$method}) { eval "#line 0 Compile iDatabase::SQL::$method\n$SUBS{$method}"; if ($@) { die "Unable to compile $pkg::$method $@"; } return $iDatabase::SQL::{$method}->( @_ ); } # Still here? forward to the driver then.. shift @_; # remove this $obj as we need to use $obj->{driver}; $AUTOLOAD =~ s/^.*:://; $obj->{driver}->$AUTOLOAD(@_); } ``` -- `Sources/iDatabase/SQL.pm:78-95` Two mechanisms in one. `%SUBS` is a hash of *source text* -- six small accessors (`driver`, `prefix`, `show_query`, `query_count`, `matched_records`, `error`) stored as heredocs at lines 112-160 -- compiled into existence on first use and installed into the symbol table so subsequent calls hit the real sub. Everything not in `%SUBS` is forwarded verbatim to the driver. This is a deliberate startup-cost optimization: a request that never asks for `query_count` never compiles `query_count`. Given that `error` is called 642 times across the tree (`out_subs.txt` section 3.4) and `query` 263 times, the saving is real but small -- six subs. Two defects in this file are worth recording, because they explain later behavior: - Lines 168-182 define `$SUBS{def_select}`, `$SUBS{def_insert}`, `$SUBS{def_update}`, `$SUBS{def_query}`, `$SUBS{def_delete}` -- forwarding wrappers keyed under names beginning `def_`. `AUTOLOAD` looks up `$SUBS{$method}` where `$method` is `select`, `insert`, and so on. The `def_` keys can never match. They are dead code; the plain AUTOLOAD forward at line 94 does the work instead, with identical effect. - `reset` (lines 103-108): ```perl sub reset { undef %CONNECTED; # Kludge alert! no strict 'refs'; eval {"$DRIVER::disconnect( \@_ )"}; } ``` `eval { ... }` is a *block* eval whose body is a string literal in void context -- it evaluates the string and discards it. And `"$DRIVER::disconnect"` interpolates a scalar named `$disconnect` in package `DRIVER`, not `$DRIVER` followed by `::disconnect`. Nothing is called. The author's own comment ("Kludge alert!") suggests he knew. In practice the mySQL driver disconnects from its own `END` and `DESTROY` blocks (`Driver/mySQL.pm:100-105`), so the connection does close. #### 4.2 The driver contract `iDatabase::Driver::Base` (300 lines) is the interface, expressed as a base class of no-op stubs that each concrete driver inherits from and overrides. A driver must provide: | method | contract | |---|---| | `newSQL($args)` | construct + connect; return blessed driver object | | `connect` / `disconnect` | transport lifecycle | | `select` | one row by primary key -> hashref | | `query` | many rows by criteria -> arrayref (or one row for `MATCH => 'ONE'`) | | `insert` | write a row; return the new primary key | | `update` | write columns by key or `WHERE` | | `delete` | remove rows by key or `WHERE` | | `count` | row count | | `create_table` / `drop_table` / `drop_tables` / `drop_database` | DDL | | `lock_table` / `release_lock` | concurrency, flat-file drivers only | | `back_up` / `table_import` | dump and restore | | `create_index` / `update_index` / `drop_index` | index maintenance | `Base` also supplies four *concrete* helpers that every driver actually uses: - `load_cfg($table)` (lines 266-288) -- `do`es `Database/config/.cfg` and populates `cur_table`, `cur_p_key`, `cur_method`, `cur_update`, `cur_ID`, `cur_DBID`, `cur_INDEX`, `all_cols`, `total_cols`, `col_name`. Every single `select`, `query`, `insert`, `update`, `delete` in every driver begins by calling it. That is one `do`-file per query -- cached in `%INC` under mod_perl, a fresh disk read and compile under mod_cgi. - `parse_where($where)` (lines 44-63) -- the pseudo-SQL translator, below. - `parse_limit($range)` (lines 73-84) -- turns `"0 to 10"` into `LIMIT 0, 11`. The comment notes pgSQL needs it "the other way around for some surreal reason." In practice `Driver/pgSQL.pm` does not override `parse_limit` at all; it ignores the inherited helper and inlines its own `OFFSET`/`LIMIT` construction at `Driver/pgSQL.pm:270-276`. The base method is therefore used only by mySQL. - `decode_record` / `encode_record` (lines 228-260) -- split and join a flat record on the literal delimiter `|^|`. `Base` defines `rebuild_record` twice, at line 164 (stub) and again at line 182 (real implementation). Perl takes the later definition; the stub is shadowed. #### 4.3 How a query is expressed Callers never write SQL. They pass named arguments: ```perl $obj->{'TOTAL_CATS'} = $db->query(TABLE => 'categories', SORT_KEY => 'CAT_POS', SORT_BY => 'A-Z', MATCH => 'ALL', ) || die $db->{'error'}; ``` -- `Sources/Boards.pm:47-51` The full argument set (`Driver/mySQL.pm:208-221`) is `TABLE`, `COLUMNS`, `SORT_KEY`, `SORT_BY` (`A-Z` / `Z-A`), `WHERE`, `MATCH` (`ONE` / `ALL` / `WITH COUNT`), `RANGE` (`"0 to 10"`), `COUNT`, `INDEX`, plus `DBID` and `ID` which are marked `# DEPRECIATED IN SQL` but still passed by callers because the flat-file drivers need them to pick a file. `WHERE` is the interesting one. It is neither SQL nor Perl but a hybrid: ```perl my $view1 = $db->query( TABLE => 'topic_views', ID => $obj->{'.forum_id'}, WHERE => "TOPIC_ID == '$topic_view1' and FORUM_ID eq '$forum_view1' and MEMBER_ID eq '$membid'", ); ``` -- `Sources/Topic.pm:67-70` Perl comparison operators (`eq`, `ne`, `==`, `!=`, `=~`, `!~`) and Perl boolean words (`and`, `or`, `&&`), on bare column names, with values in single quotes. `Base::parse_where` converts that dialect toward SQL for the DBI drivers: ```perl sub parse_where { my ($obj, $where) = @_; my %ops = ( 'eq' => '=', 'ne' => '<>', '==' => '=', '!=' => '<>', '=~' => 'REGEXP', '!~' => 'NOT REGEXP' ); my %bln = ( 'and' => 'AND', 'or' => 'OR', '&&' => 'AND' ); $where =~ s#\s{1,}(eq|ne|==|!=|=~)\s{1,}# $ops{$1} #ig; $where =~ s#\s{1,}(and|or|&&)\s{1,}# $bln{$1} #ig; # Convert perl / into single quotes... $where =~ s#/#'#g; return $where; } ``` -- `Sources/iDatabase/Driver/Base.pm:44-63` The design intent is clear: pick a syntax the flat-file drivers can execute *directly as Perl*, and translate it into SQL for the SQL drivers. That is the right way round -- the cheap backend gets the native form, the expensive backend pays the translation. #### 4.4 How the flat-file drivers emulate SQL `Driver/DBM.pm` is the flat-file driver that shipped as the default. It ties an `AnyDBM_File` hash per table (preferring `DB_File`, falling back through `GDBM_File`, `NDBM_File`, `SDBM_File` -- `Driver/DBM.pm:16`), keyed by primary key, whose values are `|^|`-joined records. `select` by primary key is a hash lookup, and is genuinely fast. `query` is where the emulation happens: ```perl if ($IN->{'WHERE'}) { $statement_string = "(".$IN->{'WHERE'}.")"; for my $i (0 .. $obj->{'total_cols'}) { $statement_string =~ s!(^|[\s\b\(])($obj->{'col_name'}->[$i])([\b\s\)]|$)!$1\$data->{'$2'}$3!g; } #> Swop SQL LIKE to perl regexp $statement_string =~ s#\s{1}NOT LIKE\s{1}# !~ #g; $statement_string =~ s#\s{1}LIKE\s{1}# =~ #g; #> We assume that /$word/ is looking for a word boundry #> and /%$word%/ is looking for any match $statement_string =~ s#/(.+?)/#/\\b$1\\b/i#g; $statement_string =~ s#/\\b%#/#g; $statement_string =~ s#%\\b/#/#g; } else { $statement_string = qq~(\$data->{ \$obj->{'cur_p_key'} } ne '')~; } ``` -- `Sources/iDatabase/Driver/DBM.pm:212-228` Every bare column name in the `WHERE` is rewritten into `$data->{'COLUMN'}`. `LIKE` becomes `=~`, `NOT LIKE` becomes `!~`, `/word/` becomes `/\bword\b/i`, `%` wildcards are stripped. The result is a Perl boolean expression, which is then compiled into a predicate function: ```perl my $eval = qq~sub Check { my (\$obj, \$data) = \@_; return 1 if $statement_string; }~; { local $@; eval $eval; die "Eval Error while parsing:: $statement_string" . $@ if $@; } ``` -- `Sources/iDatabase/Driver/DBM.pm:236-241` and applied to every record in the table: ```perl tie (my %DB, $AnyDBM_File::ISA[0], $file, O_RDWR|O_CREAT, 0777) || die "Can't open file ($file) for reading. $!"; my ($k, $v); while (($k, $v) = each(%DB)) { my $data = $obj->decode_record($v); if ($obj->Check($data)) { push @to_sort, $data; ``` -- `Sources/iDatabase/Driver/DBM.pm:245-251` So a `WHERE` clause against a DBM board is a **full table scan with a per-row Perl subroutine call**, and the predicate is recompiled from source on every query. Sorting (lines 265-288), `RANGE` slicing, and `COUNT` all happen in memory afterward, because the storage layer cannot do any of it. The `INDEX` argument is the escape hatch: `Driver/DBM.pm:174-192` redirects an indexed lookup to `query_index` + `select`, turning a scan into two hash lookups. The `Tools/create_indexes.cgi` utility in the distribution exists to build those indexes. On mySQL, `INDEX` degrades to an ordinary `WHERE key='value'` (`Driver/mySQL.pm:234-242`) because the server has real indexes. `Driver/CSV.pm` uses the same technique against line-oriented text files, with one extra generalization at `Driver/CSV.pm:229-250`: a table whose `MTD` is `multiple` is a *directory* of per-record files, and the driver `readdir`s it. The comment is honest about the cost: ```perl # Really not recommended!! # Huge resource drain on large databases. # Only added to allow mySQL intergration. ``` -- `Sources/iDatabase/Driver/CSV.pm:231-233` **`Driver/CSV.pm` is vestigial and cannot be loaded.** Three independent confirmations: (a) its package declaration is `package iDatabase;` (`Driver/CSV.pm:1`), not `iDatabase::Driver::CSV`, so `iDatabase::SQL::new`'s `$class->newSQL(...)` would call a method in the wrong package; (b) it has no `newSQL` sub at all and no `@ISA` linking it to `Driver::Base`; (c) the installer's driver menu offers only four options -- ```perl my $select = "
.cfg`, 29 files, read at runtime by `Base::load_cfg` on every single query. ```perl package IMPORT; $STRING = { "TABLE" => "forum_topics", "P_KEY" => "TOPIC_ID", "MTD" => "single", "UPDATE" => "top", "ID" => "FORUM_ID", }; %{ $COLS } = ( "TOPIC_ID" => [0 , 'update', 10 , 1], "TOPIC_TITLE" => [1 , 'string', 70, 1], "TOPIC_DESC" => [2 , 'string', 70, ], "TOPIC_STATE" => [3 , 'string', 8 ], ... ``` -- `Database/config/forum_topics.cfg:1-13` Each column is `[ ordinal, type, width, not-null ]`. The ordinal is what makes `|^|`-joined flat records decodable. The type vocabulary is `update` (an auto-incrementing primary key), `num`, `string`, `text`. **Definition two**: `INSTALL_DATA/mysql_schema.txt`, `INSTALL_DATA/postgres_schema.txt`, `INSTALL_DATA/oracle_schema.txt` -- 435, 418, and 436 lines of hand-written DDL, executed statement-by-statement by the installer (`install_modules/database.pl:228`, `:415`, `:616`) with a regex rewriting `ib_` to the chosen prefix. ```sql CREATE TABLE ib_forum_topics ( TOPIC_ID bigint(10) unsigned DEFAULT '0' NOT NULL, TOPIC_TITLE varchar(70) NOT NULL, TOPIC_DESC varchar(70), ... PRIMARY KEY (TOPIC_ID), INDEX forum_topics_idx1(FORUM_ID,PIN_STATE,TOPIC_LAST_DATE), INDEX forum_topics_idx2(WATCHED,FORUM_ID), INDEX forum_topics_idx3(TOPIC_LAST_DATE) ); ``` -- `INSTALL_DATA/mysql_schema.txt:196-222` The two definitions must agree on column names and order or the board breaks -- `load_cfg`'s `col_name` array drives `decode_record`, and `insert`/`update` skip any key not present in `all_cols` (`Driver/mySQL.pm:332`, `:436`). They do not have to agree on types, and they do not: the DDL carries indexes the `.cfg` knows nothing about, and the `.cfg` carries a `MTD`/`DBID` file-layout model the DDL knows nothing about. There is a *third*, generated definition -- `Driver/mySQL.pm:656` `create_table` derives DDL from the `.cfg` -- and it disagrees with the hand-written one. The `update` branch has a transcription error: ```perl if ($obj->{'all_cols'}->{$entry}[1] eq 'update') { if ($obj->{'all_cols'}->{$entry}[2] <= 3) { $field .= " TINYINT($obj->{'all_cols'}->{$entry}[2])"; } elsif ($obj->{'all_cols'}->{$entry}[1] <= 5) { $field .= " SMALLINT($obj->{'all_cols'}->{$entry}[2])"; ``` -- `Sources/iDatabase/Driver/mySQL.pm:683-688` The first test reads `[2]` (the width). Every subsequent test in that branch reads `[1]` -- the type *string* `'update'`, which numifies to 0. So any auto-increment key wider than 3 falls into the `SMALLINT` arm, capping it at 65,535. `TOPIC_ID` is `[0, 'update', 10, 1]`; the hand-written DDL correctly says `bigint(10) unsigned`, the generator would say `SMALLINT(10) UNSIGNED`. The `num` branch immediately below (lines 700-714) has the same structure written correctly, which is how one can tell it is a copy-paste slip rather than intent. This does not bite a normal install, because `create_table` returns early unless `_can_create` is set, and `ikonboard.cgi:286-287` sets it only for DBM. It would bite anyone using the admin CP's table-creation tools against a SQL backend. #### 4.6 The tradeoff The install guide states the goal plainly: > A host that has installed Perl 5 or better with the DB_file module installed. > Please note that although we offer MySQL databases for Ikonboard, that it is > not a requirement to have MySQL capabilities on your site in order to use this > version of iB. > -- `ib311/Install_Guide.html` In 2002 that was the whole ballgame. A $10/month shared host gave you a cgi-bin and Perl; a database was an upcharge, or unavailable. Ikonboard's competitors that required MySQL -- phpBB, vBulletin, Invision Board -- were locked out of that market. iDatabase is what let one codebase serve both the hobbyist on free hosting and the large board on a real server, and it is why Ikonboard was as widespread as it was. What it cost: - **Five drivers to maintain** (four live), 5,471 lines between them, each reimplementing `select`/`query`/`insert`/`update`/`delete` independently. They drifted: mySQL's `query` has an `INDEX` shortcut; DBM's has a `query_index` indirection; CSV's has a `POP` argument nothing else knows about. - **A query language nobody else speaks.** `WHERE "MEMBER_ID eq '$id'"` is neither SQL nor Perl. Every developer had to learn it, and the translation layers are regex substitutions on strings. - **The abstraction leaks the weakest backend upward.** Because DBM cannot do joins, no module uses joins. `Boards::ShowStart` issues three separate full-table `query` calls for categories, moderators, and forums (`Sources/Boards.pm:47-63`) and the code correlates them in Perl afterward -- `build_forumjump` does the category/forum join with a `grep` (`Lib/FUNC.pm:281`). That is exactly right for DBM and exactly wrong for MySQL. The mySQL driver supports `LIMIT`, but the flat-file `RANGE` semantics that callers actually use mean records are frequently fetched and then discarded. - **Two hand-maintained schemas** plus a third generated one that disagrees. - **A `do`-file per query.** `load_cfg` reads and compiles a config file for every single database operation. A parallel abstraction exists for search -- `Sources/Search/api.pm:29` picks `Search/API/api_.pm` at load time -- and a third for admin DDL (`Sources/iDatabase/Admin/a_base.pm` + `a_DBM`, `a_mySQL`, `a_pgSQL`, `a_Oracle`). The pattern is applied consistently; it is applied three times. --- ### 3.5 The skin engine #### 5.1 Two files per view Each view in `Skin/Default/` exists twice: a human-editable `.cfg` template and a compiled `.pm` module. There are 31 views, 275 template subroutines total (`out_skin.txt` sections 3.1-2). The board `require`s **only** the `.pm`; the `.cfg` exists solely so the admin CP can reconstruct the editing form. The `.cfg` is a flat sectioned text format: ``` HelpView [=HEADER] use strict; [=SUB-row] #=DESC #=TOP_LINE my $entry = shift; #=BODY ``` -- `Skin/Default/HelpView.cfg:1-18` The `.pm` is that same content wrapped in Perl: ```perl package HelpView; use strict; sub start { my ($one_text, $two_text, $three_text) = @_; return qq~
$iB::SKIN->{'C_ON'}
``` -- `Skin/Default/HelpView.pm:1-9` `#=TOP_LINE` becomes the subroutine prologue (argument unpacking), `#=BODY` becomes the `qq~...~` body, `[=HEADER]` becomes the package preamble. Views are called as plain package functions, not methods: `HelpView::start(...)`, `BoardsView::ForumRow(...)`, `ModCPView::startcp(...)`. #### 5.2 The compiler `Admin::SkinControl::save_skin` (the block at `Sources/Admin/SkinControl.pm:1096` onward) is the compiler. It builds both files in one pass: ```perl my $config_info = qq~[=NAME]\n$iB::IN{'PKG'}\n[=HEADER]\n$header\n~; my $module_info = qq~package $iB::IN{'PKG'};\n\n$header\n\n~; # Grab in the form data (and there is a lot!) my @input = grep { /^SUB:/ } $iB::CGI->param(); ``` -- `Sources/Admin/SkinControl.pm:1129-1134` For each subroutine, it un-escapes the HTML the browser sent back, neutralizes the delimiter, and expands the admin-facing tag vocabulary into real Perl variable syntax: ```perl # Sort out the tags... $this_sub =~ s!<!!g; # Make tidle's safe $this_sub =~ s!~!˜!g; # Convert $SKIN tags back.. $this_sub =~ s!<%SKIN:(\w+)%>!\$iB::SKIN->\{'$1'\}!ig; # Convert language tags back.. $this_sub =~ s!<%LANG:(\w+):(\w+)%>!\$$1::lang->\{'$2'\}!ig; # Convert $iB::INFO tags back.. $this_sub =~ s!<%VAR:(\w+)%>!\$iB::INFO->\{'$1'\}!ig; # Convert $iB::IN tags back.. $this_sub =~ s!<%IN:(\w+)%>!\$iB::IN\{'$1'\}!ig; # Convert $iB::MEMBER tags back.. $this_sub =~ s!<%MEMBER:(\w+)%>!\$iB::MEMBER->\{'$1'\}!ig; ``` -- `Sources/Admin/SkinControl.pm:1150-1164` then appends to both outputs: ```perl # Append to the config file. $config_info .= qq~[=SUB-$name]\n#=DESC\n$this_desc\n#=TOP_LINE\n$this_top\n#=BODY\n$this_sub\n~; # Append to our module. $module_info .= qq~sub $name {\n\t$this_top\nreturn qq\~\n$this_sub\n\~;\n}\n\n~; } # Dont forget the true value for our require file.. $module_info .= "\n\n1;"; ``` -- `Sources/Admin/SkinControl.pm:1174-1182` And -- this is the part that makes the whole scheme survivable -- it validates before it commits, by writing to a scratch file and trying to load it: ```perl open TESTFILE, ">$module.txt" or $ADMIN->Error(...); ... { # Turn off our die catcher... local $SIG{__DIE__} = undef; # Try and "require" it eval { require "$module.txt"; }; if ($@) { unlink "$module.txt"; my $error = $@; $error =~ s!$module.txt!!g; $ADMIN->Error( ... MSG => "Error: A mistake has been made when editing, this is no longer a valid perl module. No changes have been made to the existing skin modules or configuration files. ..."); } } ``` -- `Sources/Admin/SkinControl.pm:1189-1208` Only after a clean `require` are the real `.cfg` and `.pm` overwritten (lines 1211-1219). An admin cannot save a syntactically broken skin. That is a well-judged safety net for a system whose fundamental design lets an administrator type executable Perl into a `