# Technical documentation — contacts (FTI Consulting Intranet Contacts v2.2)

Evidence date: 2026-10-18.

Source: `git@bitbucket.org:overdrive/contacts.git`, `master` at `c1b9e91` (2020-12-11, "Initial commit"). Only branch. 467 files in the tree; most of them are the vendored FCKeditor.

Connection passwords are named by file and are not reproduced.

## Architecture

Classic ASP pages rendered as HTML 4.01 framesets. Each data page opens its own ADODB connection by including `sql_connect.asp`, runs SQL built with string concatenation, and writes HTML with `response.write`. There is no shared layout engine, no class library for the domain, and no separate service layer.

```text
Browser
  frames: topmenu.asp | left menu | innerBody
        |
        v
IIS (Classic ASP, web.config impersonation)
  sql_connect.asp  ->  ADODB  ->  ODBC Driver 17  ->  SQL Server reddb03 / Contacts
  contact_email.asp -> CDO.Message -> SMTP localhost:25
  fAttach.cls.asp  -> aspSmartUpload -> tmp_upload\
```

`web.config` is an IIS configuration, not an ASP.NET application. It sets:

- `system.web` / `identity impersonate="true"`
- removal of the response header `x-frame-options`
- `defaultDocument enabled="true"` with no explicit file list
- `directoryBrowse enabled="true"`

## Application structure

Product pages are flat in the repository root. `function.asp` holds `getUserID`, `getUserType`, `getUserName`, `swapIt`, and `fixquote`. `md5.asp` is the server MD5 used at login and when saving a password.

| Area | Files |
|---|---|
| Entry | `index.asp` (login), `default.asp` (writes `Hello`, unlinked) |
| Shells | `home.asp`, `search.asp`, `admin.asp`, `topmenu.asp`, `logout.asp` |
| Directory | `leftmenu.asp`, `contactlist.asp`, `contactdetail.asp` |
| Search | `search_leftmenu.asp`, `rpt_custom.asp`, `rpt_custom_1.asp`, `editCriteria.asp`, `runQuery.asp`, `printer_friendly.asp` |
| Mail | `contact_email.asp`, `fAttach.asp`, `fAttach.cls.asp` |
| Admin menu | `admin_leftmenu.asp`, `admin_lookup.asp`, `admin_letters.asp`, `letters.js` |
| Lookups | `admin_beat.asp`, `admin_category.asp`, `admin_city.asp`, `admin_country.asp`, `admin_designation.asp`, `admin_distributionlist.asp`, `admin_language.asp`, `admin_suburb.asp`, `admin_title.asp` |
| Main records | `admin_publication.asp`, `admin_show.asp`, `admin_contact.asp`, `admin_contact_beat.asp`, `admin_contact_distributionlist.asp` |
| Accounts and settings | `admin_user.asp`, `admin_user_lite.asp`, `admin_system.asp`, `admin_tools.asp` |
| Connection | `sql_connect.asp` (used), `db_connect.asp` (probe, not included) |
| Style | `style.css` |
| Editor | `FCKeditor/` |
| Images | `images/FTI_Logo_Color_300.jpg` (used), `images/FD-BEACHHEAD-logo.jpg`, `images/logo.jpg` |

Unlinked copies kept in the same root: `zcontactdetail.asp`, `zcontact_email.asp`, `zcontact_email.asp.old`, `zadmin_publication.asp`, `contact_emailBCC.asp`, `printer_friendly.backup.asp`, `printer_friendly2.asp`, `email.asp`, `testemail.asp`, `new.asp`.

Archives, not loaded by IIS as pages: `contacts.201301.zip`, `contactdetail.zip`, `contacts_title.zip`.

## Languages and frameworks

| Piece | Version / note | Evidence |
|---|---|---|
| Classic ASP / VBScript | Server pages | `<% %>` blocks |
| HTML | 4.01 Transitional, framesets | DOCTYPE on the shells |
| JavaScript | Browser validation, MD5, letter bar, query builder | `md5.js`, `letters.js`, inline scripts |
| CSS | One stylesheet | `style.css`. Body classes `bod_lite`, `bod_dark`. Dark bar colour `#F6652E` |
| FCKeditor | 2.3.2, build 1082, copyright 2003–2006 | `FCKeditor/fckeditor.js` |
| aspSmartUpload | COM `aspSmartUpload.SmartUpload` | DLLs at the repo root; `fAttach.cls.asp` |
| MD5 | Local `md5.asp` and `md5.js` | No package manager |

`.gitignore` is the Bitbucket default (Node, Java, Python, Maven, IDE, media). It does not mention ASP, DLLs, or zip files. The DLLs and zips are committed.

There is no `package.json`, `composer.json`, `.csproj`, `requirements.txt`, or solution file.

## Database

**Implemented** connection in `sql_connect.asp`:

- API: ADODB
- Driver: `{ODBC Driver 17 for SQL Server}`
- Server: `reddb03`
- Database: `Contacts`
- SQL authentication: user id and password are literals in the file

A commented line in the same file uses driver `{sql server}`, server `reddb02`, and the same database name. `db_connect.asp` repeats the active connection and selects `tblUser`.

`new.asp` uses a different connection: `{sql server}`, server `localhost`, database `bmsa`. It is not included by the product pages.

No `CREATE TABLE`, view, or stored-procedure script is in the repository. The tables and columns below are the ones named in SQL strings. Columns that exist only in the database and are never mentioned in source are **Unknown**.

### Tables and columns named in code

| Table | Columns referenced | Relationships implied by joins and filters |
|---|---|---|
| `tblUser` | `UserID`, `strUserName`, `strPW`, `strEmail`, `lngType`, `strSignature` | Owns `tblQuery.refUserID` |
| `tblSystem` | `strDisclaimer` | Single setting. Update has no `WHERE` |
| `tblCategory` | `CategoryID`, `strDescription` | `tblPublication.refCategoryID` |
| `tblLanguage` | `LanguageID`, `strDescription` | `tblPublication.refLanguageID` |
| `tblCountry` | `CountryID`, `strDescription` | `tblPublication.refPhysCountryID` (delete SQL uses `refPhyscountryID`) |
| `tblCity` | `CityID`, `strDescription` | `tblPublication.refPhysCityID` |
| `tblSuburb` | `SuburbID`, `strDescription` | `tblPublication.refPhysSuburbID` |
| `tblTitle` | `TitleID`, `strDescription` | `tblContact.refTitleID` |
| `tblDesignation` | `DesignationID`, `strDescription` | `tblContact.refDesignationID` |
| `tblBeat` | `BeatID`, `strDescription` | Junction `tblContactBeat` |
| `tblContactBeat` | `ContactBeatID`, `refBeatID`, `refContactID` | Contact to beat |
| `tblDistributionList` | `DistributionListID`, `strDescription` | Junction `tblContactDistributionList` |
| `tblContactDistributionList` | `ContactDistributionListID`, `refDistributionListID`, `refContactID` | Contact to list |
| `tblPublication` | `PublicationID`, `strName`, `refCategoryID`, `refLanguageID`, `refPhysSuburbID`, `refPhysCityID`, `refPhysCountryID`, `strLSM`, `strAge`, `strURL`, `strPost`, `strLogin`, `strPhys`, `strFootprint`, `strCopyNewsdesk`, `strDirections`, `strCirculation`, `strSwitchboard`, `strFax`, `strDescription`, `lngReach`, `strLastUser`, `strLastEdit` | Parent of contacts |
| `tblShow` | `ShowID`, `strName`, `strAppearing`, `strDeadline`, `strHost`, `strDetails`, `lngReach` | `tblContact.refShowID`, left join |
| `tblContact` | `ContactID`, `strName`, `refPublicationID`, `refShowID`, `refTitleID`, `refDesignationID`, `strTel`, `strTelAlternate`, `strFaxAlternate`, `strCell`, `strEmail`, `strEmailAlternate`, `strCarReg`, `memNotes`, `memInterests`, `strDateEdited`, `strLastUser`, `strPAName`, `strPATel`, `strPAEmail`. Also read on the printer layout: `strSwitchboard`, `strFax` | Belongs to one publication, one title, one designation, optional show |
| `tblQuery` | `QueryID`, `refUserID`, `strDescription`, `blnPrivate`, `strDateLastRun` | Owned by a user |
| `tblQueryFields` | `QueryFieldsID`, `refFieldsID`, `refQueryID`, `intOrder`, `strSort`, `strCriteria` | Fields on a query |
| `tblFields` | `FieldID`, `strName`, `strLabel` | Catalogue of selectable query expressions. `strName` is inserted into the SELECT list |

`admin_tools.asp` can add `tblContact.strLastUser nvarchar(50)`, `tblPublication.strLastUser nvarchar(50)`, and `tblPublication.strLastEdit nvarchar(10)` when `INFORMATION_SCHEMA.COLUMNS` reports a count below 1. The live save statements already write those columns, so a database that has never had the tool run would fail those saves.

Primary keys, foreign keys, indexes, and nullability are **Unknown**. Deletes in the application do not declare `ON DELETE` behaviour; they either refuse the parent delete or delete the child rows themselves.

### Date formats written by the application

| Column | Format | Writer |
|---|---|---|
| `tblContact.strDateEdited` | `DD/MM/YYYY` | `admin_contact.asp` |
| `tblPublication.strLastEdit` | `DD/MM/YYYY` | `admin_publication.asp` |
| `tblQuery.strDateLastRun` | `YYYY/MM/DD` | `runQuery.asp` |

All three are strings, not SQL datetime parameters.

## Authentication

Login is `index.asp`.

1. Browser: `MD5(password)`, then `MD5(that digest + SeedVal)`, then the password field is cleared.
2. Form method is GET, so `strUserName`, `combin`, and `SeedVal` are on the query string.
3. Server loads `tblUser` by username and compares `MD5(strPW & SeedVal)` with `combin`.
4. Cookie value is `UserID|lngType`. There is no server-side session store.

`getUserName` in `function.asp` includes `sql_connect.asp` from inside the function, which opens a second connection when a page has already included the file.

Authorisation is numeric equality with 47 or 33. It is not a role table.

Pages without the cookie redirect: `default.asp`, `new.asp`, `db_connect.asp`, `email.asp`, `testemail.asp`, `fAttach.asp`, `fAttach.cls.asp`, `logout.asp`, `title.htm`.

## Email

`contact_email.asp` uses CDO (`METADATA` points at `C:\Windows\system32\cdosys.dll`).

| Setting | Value |
|---|---|
| Send method | `cdoSendUsingPort` (2) |
| SMTP server | `localhost` |
| Port | 25 |
| Connection timeout | 10 |
| Body | HTML |
| Attachment path | `<serverPath>tmp_upload\<UserID>_<filename>` |

`serverPath` is `c:\inetpub\wwwroot\contacts\` when `SERVER_NAME` is not `sql-server`, and `c:\inetpub\wwwroot\bmsa\contacts\` when it is.

`testemail.asp` points its type library at `C:\WINNT\system32\cdosys.dll` and exposes an unauthenticated send form. It is not linked from the application.

FCKeditor on the mail page uses toolbar set `BMSA`, defined in `FCKeditor/fckconfig.js`. That set includes Source, paste from Word, tables, and font controls. `fckeditor.js` sets `BasePath` to `/fckeditor/` by default; the mail page sets `BasePath` to `FCKeditor/` before `Create`.

## Files and upload

`fAttach.cls.asp` creates `aspSmartUpload.SmartUpload`, sets `CodePage` to `utf-8`, and on Add calls `SaveAs "tmp_upload/" & UID & "_" & filename`. On Remove it deletes the file with `Scripting.FileSystemObject`. The original filename is taken from the upload. There is no allow-list.

The folder `tmp_upload` is not in the git tree. It has to exist on the IIS host. **Unknown** whether it is created by deployment or by hand.

## Request style

Most admin and directory forms use `method="get"`. Edit actions switch the form to POST in JavaScript (`document.form.method = "post"`) and then the server still reads `Request.QueryString` for the saved values. A POST body is therefore not what those save handlers read.

Exceptions that read `Request.Form`: `contact_email.asp` (send), `fAttach.cls.asp` (upload), `testemail.asp`.

`fixquote` replaces `"` with `'` and then doubles `'`. It is applied to text fields on the admin saves. Numeric ids are concatenated without it.

## Security observations

These are properties of the code, recorded so the knowledge base is accurate. This document does not describe how to exploit them.

| Topic | Where | What the code does |
|---|---|---|
| SQL credentials in source | `sql_connect.asp`, `db_connect.asp`, `new.asp` | User id and password literals. The active product account is in the first two files. `new.asp` has a different account for database `bmsa` |
| SQL built by concatenation | Data pages generally | Request values are embedded in SQL text |
| Password material on the query string | `index.asp` GET form | `combin` is submitted as a query parameter |
| Hash debug output | `index.asp` | Writes three digest values to the response before the success check |
| Password field visible | `admin_user.asp` | `type='text'` |
| Stored password | `tblUser.strPW` | MD5 digest, no salt column |
| Cookie | `index.asp` | User id and type in a cookie, no `HttpOnly` or `Secure` in the assignment |
| Directory browsing | `web.config` | Enabled |
| Framing | `web.config` | `x-frame-options` removed. The UI is itself a frameset |
| Open mail probe | `testemail.asp` | No cookie check |
| User-id probe | `db_connect.asp` | Writes a `UserID` when the connection works |
| Impersonation | `web.config` | `impersonate="true"` |

**Requires confirmation:** rotate the SQL passwords that are already in git history, and confirm whether `db_connect.asp`, `new.asp`, `email.asp`, and `testemail.asp` are reachable on the IIS site.

## Infrastructure and deployment

No pipeline, Dockerfile, CodeDeploy, or server manifest is in the repository.

What the code assumes:

| Item | Evidence |
|---|---|
| IIS site root | `c:\inetpub\wwwroot\contacts\` or `c:\inetpub\wwwroot\bmsa\contacts\` |
| Host name switch | `Request.ServerVariables("SERVER_NAME") = "sql-server"` selects the BMSA path |
| SQL Server | Host name `reddb03`, database `Contacts` |
| SMTP | `localhost` port 25 |
| COM | ADODB, CDO, aspSmartUpload, Scripting.FileSystemObject |
| ODBC | Driver 17 for SQL Server installed on the IIS machine |

`aspSmartUpload.dll` and `aspSmartUploadUtil.dll` are in the repo root. Registration (`regsvr32`) is not scripted. **Unknown** whether the live server already has them registered.

There are no background workers, queues, object-storage clients, or monitoring hooks.

## Development and testing

- No test project, no fixture, no CI config.
- No README or local-setup notes.
- `.gitignore` does not reflect the ASP stack.
- Git history is one commit, so blame and incremental review are not available.
- The login page tells the user the site is best viewed at 1024x768.

## Configuration reference

| Setting | File | Value |
|---|---|---|
| Product title | `title.htm` | FTI Consulting Intranet - Contacts - v2.2 |
| Database host | `sql_connect.asp` | `reddb03` (commented previous host `reddb02`) |
| Database name | `sql_connect.asp` | `Contacts` |
| ODBC driver | `sql_connect.asp` | ODBC Driver 17 for SQL Server |
| Cookie name | `index.asp` | `ckyUserID` |
| Admin type code | `admin_user.asp` | 47 |
| Viewer type code | `admin_user.asp` | 33 |
| SMTP | `contact_email.asp` | `localhost:25` |
| Summary Bcc | `contact_email.asp` | `fdsouthafrica@fd.com` |
| IIS paths | `contact_email.asp`, `fAttach.asp` | `c:\inetpub\wwwroot\contacts\` or `...\bmsa\contacts\` |
| Editor | `FCKeditor/fckeditor.js` | 2.3.2 |

## Discrepancies inside the implementation

1. Journalist tab reads publication switchboard and fax. Printer layout reads the contact columns. Contact save no longer writes those contact columns.
2. Suburb delete SQL checks `tblPublication`. The alert says the rows are linked to Contacts.
3. Country delete filters `refPhyscountryID`. Insert and update write `refPhysCountryID`.
4. `admin_tools.asp` decides to show a schema button by looking at the Notes field expression, then the button adds different columns.
5. `strSignature` is stored and edited, and `contact_email.asp` loads it without using it.
6. `memInterests` is stored and selected, and the card does not render it.
7. `strCarReg` is labelled Twitter Handle.
8. Query privacy does not filter `runQuery.asp`.
9. Several `z*` and probe pages duplicate or predate the live pages and are not linked.
10. Page title and logo say FTI. Paths, the editor toolbar name, and `email.asp` still say BMSA. One logo file is named FD Beachhead and is unused. The summary mail Bcc is an `fd.com` address.

## Unknown / Requires Confirmation

- Live URL, IIS bindings, and whether directory browsing is enabled in production as well as in `web.config`.
- Whether `reddb03` is still the server, and whether ODBC Driver 17 is installed there.
- Whether `tmp_upload` exists and which identity can write it under impersonation.
- Full schema, including keys and the contents of `tblFields`.
- Whether the DLLs in the repo root are the versions registered on the server.
- Whether the 2011 column-add tool has already been run on the database that the save statements expect.
