|
| 1 | +--- |
| 2 | +name: directadmin-api-call |
| 3 | +description: Creates a DirectAdmin API call using HTTPSocket for connect/authenticate/query/parse pattern. Use when user says 'call DirectAdmin API', 'query DA endpoint', 'add DA command', or adds new bin/ scripts. Covers SSL connection on port 2222, set_login, query with options array, fetch_parsed_body, and error checking via $result['error']. Do NOT use for modifying src/HTTPSocket.php itself or for non-DirectAdmin API integrations. |
| 4 | +--- |
| 5 | +# DirectAdmin API Call |
| 6 | + |
| 7 | +## Critical |
| 8 | + |
| 9 | +- **Never hardcode credentials** in committed files — `$hash`/`$password` must come from DB (`backup_key`) or be placeholder comments only |
| 10 | +- Always use the `'ssl://'` stream prefix for production connections — plain HTTP is only for local debug (seen commented out in bin scripts) |
| 11 | +- Check `$result['error'] != "0"` (string `"0"`, not int) — this is DirectAdmin's convention |
| 12 | +- Always call `fetch_parsed_body()` immediately after `query()` before any other query |
| 13 | +- In `src/Plugin.php` event handlers, always call `$event->stopPropagation()` after handling |
| 14 | + |
| 15 | +## Instructions |
| 16 | + |
| 17 | +1. **Import HTTPSocket** at the top of the file: |
| 18 | + ```php |
| 19 | + use Detain\MyAdminDirectAdminStorage\HTTPSocket; |
| 20 | + ``` |
| 21 | + For `bin/` scripts also add: `require_once('../vendor/autoload.php');` |
| 22 | + For DB-accessing bin scripts: `include_once __DIR__.'/../../../../include/functions.inc.php';` |
| 23 | + |
| 24 | +2. **Get server credentials** — in Plugin event handlers use: |
| 25 | + ```php |
| 26 | + $settings = get_module_settings(self::$module); // self::$module = 'backups' |
| 27 | + $serverdata = get_service_master($serviceClass->getServer(), self::$module); |
| 28 | + $hash = $serverdata[$settings['PREFIX'].'_key']; |
| 29 | + $ip = $serverdata[$settings['PREFIX'].'_ip']; |
| 30 | + ``` |
| 31 | + In bin/ scripts query the DB directly: |
| 32 | + ```php |
| 33 | + $db = get_module_db('backups'); |
| 34 | + $db->query("select * from backup_masters where backup_name='".$db->real_escape($argv[1])."'", __LINE__, __FILE__); |
| 35 | + $db->next_record(MYSQL_ASSOC); |
| 36 | + $ip = $db->Record['backup_ip']; |
| 37 | + $password = $db->Record['backup_key']; |
| 38 | + ``` |
| 39 | + Verify credentials are non-empty before continuing. |
| 40 | + |
| 41 | +3. **Connect and authenticate:** |
| 42 | + ```php |
| 43 | + $sock = new HTTPSocket(); |
| 44 | + $sock->connect('ssl://'.$ip, 2222); |
| 45 | + $sock->set_login('admin', $hash); |
| 46 | + ``` |
| 47 | + Use `($server_ssl == 'Y' ? 'ssl://' : '').$ip` if SSL is configurable. |
| 48 | + |
| 49 | +4. **Build `$apiCmd` and `$apiOptions`, then query:** |
| 50 | + - POST with params array (most mutating commands): |
| 51 | + ```php |
| 52 | + $apiCmd = '/CMD_API_SELECT_USERS'; |
| 53 | + $apiOptions = [ |
| 54 | + 'location' => 'CMD_SELECT_USERS', |
| 55 | + 'suspend' => 'Suspend', |
| 56 | + 'select0' => $username, |
| 57 | + ]; |
| 58 | + $sock->query($apiCmd, $apiOptions); |
| 59 | + ``` |
| 60 | + - GET with query string (read-only commands): |
| 61 | + ```php |
| 62 | + $sock->query('/CMD_API_SHOW_USER_CONFIG?user='.$username); |
| 63 | + ``` |
| 64 | + Verify `$apiCmd` starts with `/CMD_API_`. |
| 65 | + |
| 66 | +5. **Fetch and check result:** |
| 67 | + ```php |
| 68 | + $result = $sock->fetch_parsed_body(); |
| 69 | + if ($result['error'] != "0") { |
| 70 | + // error path |
| 71 | + myadmin_log('directadmin', 'error', 'Error: Text:'.$result['text'].' Details:'.$result['details'], __LINE__, __FILE__, self::$module, $serviceClass->getId()); |
| 72 | + $event['success'] = false; |
| 73 | + $event->stopPropagation(); |
| 74 | + return; |
| 75 | + } |
| 76 | + ``` |
| 77 | + Special terminate case — also treat `$result['text'] == "System user {$username} does not exist!"` as success. |
| 78 | + |
| 79 | +6. **Log the call** (required in Plugin event handlers): |
| 80 | + ```php |
| 81 | + request_log(self::$module, $serviceClass->getCustid(), __FUNCTION__, 'directadmin', $apiCmd, $apiOptions, $result, $serviceClass->getId()); |
| 82 | + myadmin_log('myadmin', 'info', 'DirectAdmin '.$apiCmd.' '.json_encode($apiOptions).' Response: '.json_encode($result), __LINE__, __FILE__, self::$module, $serviceClass->getId()); |
| 83 | + ``` |
| 84 | + |
| 85 | +## Examples |
| 86 | + |
| 87 | +**User says:** "Add a bin script to unsuspend a user by hostname" |
| 88 | + |
| 89 | +**Actions taken:** |
| 90 | +1. Create `bin/unsuspend_user.php` |
| 91 | +2. Include functions, query `backup_masters` for credentials by hostname arg |
| 92 | +3. Connect with `'ssl://'.$ip` on port 2222, `set_login('admin', $password)` |
| 93 | +4. Query `/CMD_API_SELECT_USERS` with `dounsuspend=y`, `select0=$username` |
| 94 | +5. `fetch_parsed_body()`, `print_r($result)` |
| 95 | + |
| 96 | +**Result:** |
| 97 | +```php |
| 98 | +<?php |
| 99 | +use Detain\MyAdminDirectAdminStorage\HTTPSocket; |
| 100 | +include_once __DIR__.'/../../../../include/functions.inc.php'; |
| 101 | +$db = get_module_db('backups'); |
| 102 | +if (count($_SERVER['argv']) < 3) { |
| 103 | + die("Usage: {$_SERVER['argv'][0]} <hostname> <username>\n"); |
| 104 | +} |
| 105 | +$db->query("select * from backup_masters where backup_name='".$db->real_escape($_SERVER['argv'][1])."'", __LINE__, __FILE__); |
| 106 | +if ($db->num_rows() == 0) { die("Invalid server\n"); } |
| 107 | +$db->next_record(MYSQL_ASSOC); |
| 108 | +$sock = new HTTPSocket(); |
| 109 | +$sock->connect('ssl://'.$db->Record['backup_ip'], 2222); |
| 110 | +$sock->set_login('admin', $db->Record['backup_key']); |
| 111 | +$sock->query('/CMD_API_SELECT_USERS', [ |
| 112 | + 'location' => 'CMD_SELECT_USERS', |
| 113 | + 'dounsuspend' => 'y', |
| 114 | + 'select0' => $_SERVER['argv'][2], |
| 115 | +]); |
| 116 | +$result = $sock->fetch_parsed_body(); |
| 117 | +print_r($result); |
| 118 | +``` |
| 119 | + |
| 120 | +## Common Issues |
| 121 | + |
| 122 | +- **`$result['error']` is always `"1"` / empty response:** SSL handshake failed. Verify the server actually has a valid cert on port 2222: `openssl s_client -connect $ip:2222`. If self-signed, `HTTPSocket` still accepts it (verify_peer is off by default). |
| 123 | +- **`fetch_parsed_body()` returns `null` or non-array:** Called a second time after already fetching, or `query()` was never called. Always call `query()` then `fetch_parsed_body()` in sequence. |
| 124 | +- **`result['text']` = `"Cannot Login"` :** Wrong `$hash`/password. Confirm `backup_key` column value in `backup_masters` matches the DirectAdmin admin password. |
| 125 | +- **`result['details']` = `"Sorry, the password..."` :** DirectAdmin rejects the generated password. Retry with `generateRandomString(10, 2, 2, 2, 1)` in a loop as done in `src/Plugin.php` around the activate method. |
| 126 | +- **`result['details']` = `"Sorry, a group for that username already exists."` :** Username collision. Append/rotate a character: `$username = mb_substr($username.'a', 1)` and retry (see `src/Plugin.php` activate handler). |
0 commit comments