# Technical documentation — fim

Evidence date: 2026-10-15. Source: `git@bitbucket.org:overdrive/fim.git` at `41df15e` (`master`, 2020-04-23). Author of all three commits: `JacquesOverdrive`.

No README, `composer.json`, Dockerfile, pipeline, or test suite is in the tree. Runtime facts below come from the three PHP files and the fixture directory.

## Application structure

```text
fim.cls.php          class NemoFIM (929 lines)
fim.config.php       settings, mysqli connect, print_rr, commented DDL
fim.cron.php         request entry
folder_structure/    fixture tree, not loaded by include
sesFile.txt          created at runtime; not in git
```

Tracked file count at `41df15e`: 36. The class, config, and cron are the application. Everything under `folder_structure/` was added in `8e2c48d` and not edited since.

| Path | Bytes / content | Role |
|---|---|---|
| `folder_structure/root_doc1.php`, `root_doc2.php`, `root_doc3.php` | Empty | Fixture |
| `folder_structure/Folder_1/FolderOne_Code1.php` | The text `test` | Fixture |
| `folder_structure/Folder_1/FolderOne_Code2.php` through `Code6.php` | Empty | Fixture |
| `folder_structure/Folder_Docs/Doc1.txt` through `Doc10.txt` | Empty | Fixture |
| `folder_structure/Folder_Images/images.jpg` through `images5.jpg` | JPEG bytes (about 4 KB to 34 KB) | Fixture |
| `folder_structure/Folder_Random/Excel1.xlsx` through `Excel3.xlsx` | Spreadsheet bytes (about 6 KB each) | Fixture. Excluded by `%.xlsx` if the walker sees the basename |
| `folder_structure/Folder_Random/Powerpoint1.pptx` through `Powerpoint3.pptx` | Empty | Fixture |
| `folder_structure/Folder_Random/Word1.rtf` through `Word3.rtf` | `{\rtf1}` | Fixture |

Web mode sets the scan root to `DOCUMENT_ROOT/fim/folder_structure`, so this tree is the sample the web path was pointed at. CLI mode scans the directory of `fim.cron.php` after stripping `fim_2.0`, which includes the PHP scripts as well as `folder_structure/`.

## Language and runtime

- PHP, short files, no namespace, no autoload. Class `NemoFIM`.
- Extensions used: `mysqli`, standard filesystem (`scandir`, `fileperms`, `fileowner`, `filesize`, `filemtime`, `sha1_file`, `chmod`), `mail`.
- No PHP version constraint. The commented DDL uses `current_timestamp() ON UPDATE current_timestamp()`, which is MySQL 5.6.5+ syntax (or a current MariaDB). That does not prove the PHP version.
- `fim.config.php` sets `display_errors` to `1` (twice), `error_reporting` to `E_ALL` without strict, notice, warning, or deprecated, `max_execution_time` to 1200, and `memory_limit` to `256M`.

**Unknown / Requires Confirmation:** the PHP version and operating system used when this last ran.

## Request entry

`fim.cron.php`:

1. Include config, then class.
2. If `sesFile.txt` is missing, remember to delete today's environment rows, then create the file.
3. If it exists, read all lines into `$arrSession` with `fgets` and close it.
4. Read `$_REQUEST["Action"]` (empty string when absent).
5. Dispatch. The default branch either compares or records. See the PRD workflows.

`fopen` failure on the create path calls `die("Unable to open file!")`.

The working directory for `sesFile.txt` is the process cwd, not a path next to the script. **Inferred** from the relative filename. A cron whose cwd is not the script directory will read and write a different file.

## Configuration values

All of the following are literals in `fim.config.php`. None are read from the environment.

| Setting | Committed value | Used for |
|---|---|---|
| `$dbHostname` | `localhost` | `mysqli_connect` |
| `$dbUsername` | `root` | `mysqli_connect` |
| `$dbPassword` | empty string | `mysqli_connect` |
| `$dbDatabase` | `fim` | `mysqli_connect` |
| `$siteProjectName` | `Test_Project` | Mail subject |
| `$siteEnviroment` | `qa` | Table suffix, `strLastUser`, mail subject, caption |
| `$FIMBaseURL` | `localhost/fim/` | Prefix of Ignore / Update links. No scheme |
| `$siteDirectory` | CLI: directory of `argv[0]` with `fim.cron.php` and `fim_2.0` removed. Web: `DOCUMENT_ROOT/fim/folder_structure` | Walk root and path prefix |
| `$siteWildcards` | See `business-rules.md` BR-004 | Walker and SQL excludes |
| `$processUseTimer` | `1` | Enable the second-based stop |
| `$ProcessMaxRuntime` | `5` | Seconds |
| `$processUseCounter` | `0` | Counter stop is off |
| `$ProcessMaxCount` | `10` | Unused while the counter flag is 0 |
| `$processStartTime` | `date("Y-m-d H:i:s")` at include time | Timer origin |
| Mail recipients and From | `jacques@overdrive.co.za` | `mail()` |
| Mail subject | `FIM: {project} {environment} Report {datetime}` | `mail()` |
| Mail content-type | `text/html; charset=iso-8859-1` | Header |
| Redirect on timer | `http://www.redirect.to.url.com/` | Hardcoded in `NemoFIM::Record`, not in config |

`$siteBaseURL` remains in the `global` line and is not assigned. Before `41df15e` it was `americanshutters.overdriveqa.co.za/`. That hostname is historical only.

`$FIMCronPath` is assigned (`argv[0]` or `PHP_SELF`) and is not read by the class or the cron switch.

A block still fills `$SystemSettings[SERVER_PROTOCOL]` and `$SystemSettings[SCRIPT_NAME]` from `$_SERVER[SERVER_PROTOCOL]`, `$_SERVER[HTTPS]`, and `$_SERVER[SCRIPT_NAME]` using bare constants (`SERVER_PROTOCOL`, `HTTPS`, `SCRIPT_NAME`, `SERVER_NAME` elsewhere). Those `$SystemSettings` values are not copied onto `$FIMBaseURL`.

`mysqli_connect` has no error handling. The handle is `$conn`.

## Class `NemoFIM`

| Method | Visibility | Called from | Notes |
|---|---|---|---|
| `__construct` | public | `new NemoFIM()` in the default cron branch | Splits `$siteWildcards` onto `$this->arrExcludeWildcard`. Stores `$dirRoot` on `$this->dirRoot`. `Record()` does not read `$this->dirRoot` |
| `iniSetPermissions` | public | `Record()` after each save | chmod `0755` or `0644` |
| `Record` | public | Default cron branch | Recursive walk. Writes `sesFile.txt` |
| `Save` | protected | `Record()` | `INSERT` into `sysAudit_{env}` |
| `Compare` | public static | Cron, when pause file says complete | Builds HTML and may `mail()` |
| `in_array_wildcard` | public static | `Record()` | `%` matcher. `$Debug` prints when non-zero. Callers pass `0` |
| `PullData` | public static | Nothing in this repo | Requires `$DATABASE_SETTINGS`, `$xdb`, `NemoDatabase` |
| `Ignore` | public static | Action `FIM: IGNORE` | |
| `Update` | public static | Action `FIM: UPDATE` | Prints arguments via `print_rr` first |
| `ResetMaster` | public static | Action `FIM: RESET MASTER` | `TRUNCATE` then `INSERT SELECT` |

`PullData` and `ResetMaster` both declare `global $DATABASE_SETTINGS, $BR, $SP, $TR, $SystemSettings, $DT, $MYSQLI, $xdb`. `ResetMaster` does not use them. Its SQL uses `$conn`. `PullData` connects with `mysqli_connect` and then calls `$xdb` and `new NemoDatabase`. Those symbols are undefined in this repository. The commented include at the top of `fim.cls.php` is `_nemo.email.cls.php`, which is also absent. Compare uses `mail()`, not that class.

## Database

There is no migration directory. The only schema text is the commented script in `fim.config.php`.

Queries assume:

- `sysAudit_master`
- `sysAudit_` plus the environment name (`sysAudit_qa` with the committed config)

The commented `CREATE TABLE` uses lowercase `` `sysaudit_master` `` and `` `sysaudit_{env}` ``. Case-sensitive MySQL would not treat those as the names in the queries.

Insert columns in `Save()`: `dtAudit`, `strType`, `strPath`, `strOwner`, `strPermission`, `dblSize`, `dtModify`, `strLastUser`, `strHash`, `strExtension`. Values are interpolated inside single quotes. No `mysqli_real_escape_string` and no prepared statements. `dtAudit` is `date("Y-m-d H:i:s")` while the commented column type is `date`.

`strPath` is compared with `=` and with `NOT LIKE`. The commented master column is `varchar(255)` with `utf8_bin`, so comparison would be byte-case-sensitive if that DDL were applied. The script does not create the column, so the live collation is **Unknown / Requires Confirmation**.

`Update()` for new rows selects `dtLastEdit`. `Save()` does not insert `dtLastEdit`. If the column exists only as a timestamp default, the select still returns it. If the column does not exist, that update path fails.

Connection failure behaviour: subsequent `mysqli_query` calls warn or return false, and the code does not branch on that.

## SQL injection and request trust

`ID`, `state`, and `Action` arrive in `$_REQUEST`. `ID` and the wildcard fragments are concatenated into SQL. `state` is a PHP `switch`, not interpolated, except that it selects which statement runs. This is **Implemented** behaviour. It is recorded so a later change does not treat the endpoint as authenticated.

No session cookie, token, or allow-list protects `FIM: RESET MASTER`.

## Mail

`Compare()` builds one HTML string. Summary counts come from three aggregate queries. Detail rows are capped with `LIMIT 0,50`.

The multipart preamble that used to assign `$email_message` is commented out. The next line is `$email_message .= $strBody .= "".PHP_EOL;`, so the variable is read before it is set in this file. Provenance: **Implemented** at `41df15e`. Whether that still sends a usable body depends on PHP's handling of the undefined variable under the current `error_reporting` mask (notices are excluded). **Requires Confirmation** on a real PHP runtime.

`mail()` is called with the configured recipient, subject, body, headers, and an empty fifth parameter. The return value is discarded. If all three counts are zero, `mail()` is not called.

Header construction also appends ` boundary="..."`. Combined with `Content-type: text/html`, that boundary is not a complete multipart message. The body is raw HTML.

## Control flow details worth keeping

- `Record()` sets `$intProcessComplete = 1` and writes that to `sesFile.txt` before the loop. A crash mid-walk can leave the flag at `1` even though the walk did not finish. The next request will compare instead of resuming. **Inferred** from the write happening first.
- Timer pause writes the flag `0` only when the limit trips, replacing the `1`.
- Skip window: while `$intGlobalCounter <= $intProcessPausedID`, directories are recursed into and not saved; files are not saved. Counter increment sits inside the "not excluded" branch, so excluded names do not consume an id.
- `header("Location: ...")` then `exit` on the timer path. CLI has no browser. The header is still emitted to the SAPI.
- `Compare()` is an instance call (`$fim->Compare()`) on a static method. That is valid PHP.
- `print_rr` wraps `print_r` in `<PRE>`. It runs for every recorded path, so a cron log or HTTP response contains the walk.

## Security observations

These are properties of the committed tree, not a pentest procedure.

- Database user `root` with an empty password, committed in clear text.
- `display_errors` enabled.
- Unauthenticated state-changing URLs.
- SQL built by concatenation, including the exclude list and `$_REQUEST["ID"]`.
- The walk chmods whatever it is allowed to chmod under the scan root.
- `sha1` and `sha1_file` are used as change detectors, not as a password hash.

## Infrastructure, deployment, jobs, monitoring

| Area | In the repository |
|---|---|
| Crontab or scheduler | Not present. The file is named `fim.cron.php` |
| CI / Bitbucket pipeline | Not present |
| SonarQube | Not present in git. The onboarding issue listed `sonar-project.properties` and `sonarqube_scan.bat`. They are not tracked at `41df15e` |
| Containers, web server config | Not present |
| Queue, worker, cache | Not present. Pause state is a local text file |
| Logging | `print_rr` to the response. No log file |
| Tests | Not present |
| Secrets outside the PHP file | Not present. Credentials are in `fim.config.php` |

## Development and test process

The history is three commits on `master` in April 2020. There is no branch model, review trail, or test command in the repo. The fixture tree is the only stand-in for data, and it was not wired to an assertion.

`folder_structure` filenames are stable evidence of what a scan would see. They are not a specification of expected hashes.

## Related code in other Silicon Ark projects

Documented elsewhere, not re-verified against those source trees in this pass:

- `timesheet` describes `fim_2.0/fim.config_live.php`, `fim.config_qa.php`, and `fim.config_dev.php`.
- `sawis_api` and `sawis_api_old` describe `cron.fim.php` and `_framework/_nemo.fim.cls.php`.

This tree's class name is `NemoFIM`, and CLI paths strip `fim_2.0`. That is a naming link. It is not proof of a shared history. **Unknown / Requires Confirmation.**
