|
| 1 | +--- |
| 2 | +name: abuse-record-insert |
| 3 | +description: Inserts a complete abuse incident across the `abuse` and `abuse_data` tables using make_insert_query(). Use when adding new code that reports an abuse incident — manual, CSV import, or IMAP-triggered. Covers the exact field list for both tables, getLastInsertId(), ImapAbuseCheck::fix_headers() for abuse_headers, and clientMail() notification via client/abuse.tpl. Trigger phrases: 'insert abuse record', 'report abuse', 'add abuse entry', 'log abuse incident'. Do NOT use for querying, updating, or deleting existing abuse records. |
| 4 | +--- |
| 5 | +# Abuse Record Insert |
| 6 | + |
| 7 | +## Critical |
| 8 | + |
| 9 | +- **Never use PDO.** Always use `$db = get_module_db('default')` and `make_insert_query()`. |
| 10 | +- **Never interpolate raw `$_GET`/`$_POST` into SQL.** Use `$db->real_escape()` or `make_insert_query()`. |
| 11 | +- **Always validate the IP first** with `validIp($ip, false)` before any DB write. |
| 12 | +- **Always resolve the owner** via `get_server_from_ip($ip)` and verify `$server_data['email'] != ''` before inserting. |
| 13 | +- **`abuse_id` must be `null`** in the `abuse` insert — the DB auto-increments it. |
| 14 | +- **`abuse_status` must be `'pending'`** for all new records. |
| 15 | +- Valid `abuse_type` values: `'scanning'`, `'hacking'`, `'spam'`, `'child porn'`, `'phishing site'`, `'other'`, `'uceprotect'`, `'trendmicro'`. |
| 16 | + |
| 17 | +## Instructions |
| 18 | + |
| 19 | +1. **Get the DB handle** |
| 20 | + |
| 21 | + ```php |
| 22 | + $db = get_module_db('default'); |
| 23 | + ``` |
| 24 | + |
| 25 | + Verify `$db` is not null before proceeding. |
| 26 | + |
| 27 | +2. **Validate the IP and resolve the server owner** |
| 28 | + |
| 29 | + ```php |
| 30 | + if (!validIp($ip, false)) { |
| 31 | + // skip or surface an error — do not insert |
| 32 | + } |
| 33 | + $server_data = get_server_from_ip($ip); |
| 34 | + if (!isset($server_data['email']) || $server_data['email'] == '') { |
| 35 | + add_output('Error Finding Owner For ' . $ip . '<br>'); |
| 36 | + return; |
| 37 | + } |
| 38 | + $email = $server_data['email']; |
| 39 | + ``` |
| 40 | + |
| 41 | + `$server_data['email']` is the billing contact; `$server_data['email_abuse']` is the address to notify (may differ). |
| 42 | + |
| 43 | +3. **Insert the `abuse` row** |
| 44 | + |
| 45 | + Use `mysql_now()` for current-time inserts, or a pre-formatted `MYSQL_DATE_FORMAT` string for historical dates (e.g. from CSV). |
| 46 | + |
| 47 | + ```php |
| 48 | + $db->query(make_insert_query('abuse', [ |
| 49 | + 'abuse_id' => null, |
| 50 | + 'abuse_time' => mysql_now(), // or $date->format(MYSQL_DATE_FORMAT) |
| 51 | + 'abuse_ip' => $ip, |
| 52 | + 'abuse_type' => $type, // one of the valid values listed in Critical |
| 53 | + 'abuse_amount' => 1, |
| 54 | + 'abuse_lid' => $email, |
| 55 | + 'abuse_status' => 'pending', |
| 56 | + ]), __LINE__, __FILE__); |
| 57 | + $id = $db->getLastInsertId('abuse', 'abuse_id'); |
| 58 | + ``` |
| 59 | + |
| 60 | + Verify `$id > 0` before proceeding to Step 4. |
| 61 | + |
| 62 | +4. **Insert the `abuse_data` row** (when raw headers/body are available) |
| 63 | + |
| 64 | + Always pass headers through `ImapAbuseCheck::fix_headers()` to strip HTML and normalise newlines. |
| 65 | + |
| 66 | + ```php |
| 67 | + $db->query(make_insert_query('abuse_data', [ |
| 68 | + 'abuse_id' => $id, |
| 69 | + 'abuse_headers' => ImapAbuseCheck::fix_headers($rawHeaders), |
| 70 | + ]), __LINE__, __FILE__); |
| 71 | + ``` |
| 72 | + |
| 73 | + For IMAP-sourced records the class concatenates plain + HTML message bodies: |
| 74 | + |
| 75 | + ```php |
| 76 | + 'abuse_headers' => trim(ImapAbuseCheck::fix_headers($this->plainmsg . $this->htmlmsg)), |
| 77 | + ``` |
| 78 | + |
| 79 | + Skip this step only when no header/body content exists (e.g. bare IP-list CSV imports without evidence). |
| 80 | + |
| 81 | +5. **Build and send the client notification email** |
| 82 | + |
| 83 | + The template key is always `'client/abuse.tpl'`. The link key used in the template is `md5($id . $ip . $type)`. |
| 84 | + |
| 85 | + ```php |
| 86 | + $subject = 'InterServer Abuse Report for ' . $ip; |
| 87 | + $message = str_replace( |
| 88 | + ['{$email}', '{$ip}', '{$type}', '{$count}', '{$id}', '{$key}'], |
| 89 | + [ |
| 90 | + $server_data['email_abuse'], |
| 91 | + $ip, |
| 92 | + $type, |
| 93 | + $amount, // 1 for most sources; $amount for manual single-IP form |
| 94 | + $id, |
| 95 | + md5($id . $ip . $type), |
| 96 | + ], |
| 97 | + $email_template // loaded earlier from the abuse email template file |
| 98 | + ); |
| 99 | + (new \MyAdmin\Mail())->clientMail($subject, $message, $server_data['email_abuse'], 'client/abuse.tpl'); |
| 100 | + ``` |
| 101 | + |
| 102 | + Send to `$server_data['email_abuse']`, not `$email` (they can differ). |
| 103 | + |
| 104 | +6. **Confirm to the admin UI** |
| 105 | + |
| 106 | + ```php |
| 107 | + add_output('Abuse Entry for ' . $ip . ' Added - Emailing ' |
| 108 | + . ($server_data['email_abuse'] !== $email |
| 109 | + ? $server_data['email_abuse'] . ' (for client ' . $email . ')' |
| 110 | + : $server_data['email_abuse']) |
| 111 | + . '<br>'); |
| 112 | + ``` |
| 113 | + |
| 114 | +## Examples |
| 115 | + |
| 116 | +**User says:** "Add code that manually reports a single IP for spam abuse and emails the owner." |
| 117 | + |
| 118 | +**Actions taken:** |
| 119 | + |
| 120 | +```php |
| 121 | +$db = get_module_db('default'); |
| 122 | +$ip = $db->real_escape($GLOBALS['tf']->variables->request['ip']); |
| 123 | +$type = 'spam'; |
| 124 | + |
| 125 | +if (!validIp($ip, false)) { |
| 126 | + add_output('Invalid IP<br>'); |
| 127 | + return; |
| 128 | +} |
| 129 | + |
| 130 | +$server_data = get_server_from_ip($ip); |
| 131 | +if (!isset($server_data['email']) || $server_data['email'] == '') { |
| 132 | + add_output('Error Finding Owner For ' . $ip . '<br>'); |
| 133 | + return; |
| 134 | +} |
| 135 | +$email = $server_data['email']; |
| 136 | + |
| 137 | +// Step 3 — abuse row |
| 138 | +$db->query(make_insert_query('abuse', [ |
| 139 | + 'abuse_id' => null, |
| 140 | + 'abuse_time' => mysql_now(), |
| 141 | + 'abuse_ip' => $ip, |
| 142 | + 'abuse_type' => $type, |
| 143 | + 'abuse_amount' => 1, |
| 144 | + 'abuse_lid' => $email, |
| 145 | + 'abuse_status' => 'pending', |
| 146 | +]), __LINE__, __FILE__); |
| 147 | +$id = $db->getLastInsertId('abuse', 'abuse_id'); |
| 148 | + |
| 149 | +// Step 4 — abuse_data row |
| 150 | +$rawHeaders = $GLOBALS['tf']->variables->request['headers'] ?? ''; |
| 151 | +$db->query(make_insert_query('abuse_data', [ |
| 152 | + 'abuse_id' => $id, |
| 153 | + 'abuse_headers' => ImapAbuseCheck::fix_headers($rawHeaders), |
| 154 | +]), __LINE__, __FILE__); |
| 155 | + |
| 156 | +// Step 5 — notify |
| 157 | +$subject = 'InterServer Abuse Report for ' . $ip; |
| 158 | +$message = str_replace( |
| 159 | + ['{$email}', '{$ip}', '{$type}', '{$count}', '{$id}', '{$key}'], |
| 160 | + [$server_data['email_abuse'], $ip, $type, 1, $id, md5($id . $ip . $type)], |
| 161 | + $email_template |
| 162 | +); |
| 163 | +(new \MyAdmin\Mail())->clientMail($subject, $message, $server_data['email_abuse'], 'client/abuse.tpl'); |
| 164 | + |
| 165 | +// Step 6 — UI feedback |
| 166 | +add_output('Abuse Entry for ' . $ip . ' Added - Emailing ' . $server_data['email_abuse'] . '<br>'); |
| 167 | +``` |
| 168 | + |
| 169 | +**Result:** One row in `abuse` with `abuse_status='pending'`, one row in `abuse_data` with sanitised headers, and a notification email sent to the abuse contact. |
| 170 | + |
| 171 | +## Common Issues |
| 172 | + |
| 173 | +**`make_insert_query` produces a query with wrong column count or missing fields:** |
| 174 | +- Ensure `abuse_id => null` is present — omitting it causes an auto-increment mismatch. |
| 175 | +- `abuse_lid` must be the billing email string, not an integer ID. |
| 176 | + |
| 177 | +**`getLastInsertId` returns 0 or null:** |
| 178 | +- The `make_insert_query()` call failed silently. Always pass `__LINE__, __FILE__` as the 2nd/3rd args to `$db->query()` so errors surface in logs. |
| 179 | +- Confirm the `abuse` table exists and the DB handle is for `'default'`, not `'mail'`. |
| 180 | + |
| 181 | +**`ImapAbuseCheck::fix_headers()` not found:** |
| 182 | +- The class file is not autoloaded. Ensure `function_requirements('class.ImapAbuseCheck')` has been called, or that the file is included via `add_requirement` in `Plugin::getRequirements()`. |
| 183 | + |
| 184 | +**`clientMail()` sends to the wrong address:** |
| 185 | +- Use `$server_data['email_abuse']`, not `$server_data['email']`. These can be different accounts (e.g., resellers). |
| 186 | + |
| 187 | +**Email template placeholders not replaced:** |
| 188 | +- The `str_replace` keys must exactly match `{$email}`, `{$ip}`, `{$type}`, `{$count}`, `{$id}`, `{$key}` — curly braces and dollar signs included. |
| 189 | +- `$email_template` must be loaded from the abuse email template before the `str_replace` call. Check that `$email_template` is not empty. |
| 190 | + |
| 191 | +**Historical date inserts (CSV import) showing wrong timestamps:** |
| 192 | +- Do not use `mysql_now()` for CSV rows. Parse the source date with `new \DateTime(...)` and format with `$date->format(MYSQL_DATE_FORMAT)` before passing to `make_insert_query`. |
0 commit comments