package cpanel import ( "encoding/json" "fmt" "io" "net/http" "net/url" "os" "time" ) // Client handles cPanel UAPI interactions type Client struct { host string username string apiToken string http *http.Client } // NewClient creates a new cPanel API client func NewClient() *Client { return &Client{ host: os.Getenv("CPANEL_HOST"), username: os.Getenv("CPANEL_USERNAME"), apiToken: os.Getenv("CPANEL_API_TOKEN"), http: &http.Client{ Timeout: 30 * time.Second, }, } } // NewClientWithConfig creates a client with custom config func NewClientWithConfig(host, username, apiToken string) *Client { return &Client{ host: host, username: username, apiToken: apiToken, http: &http.Client{ Timeout: 30 * time.Second, }, } } // EmailAccount represents a cPanel email account type EmailAccount struct { Email string `json:"email"` Login string `json:"login"` Domain string `json:"domain"` DiskUsed string `json:"diskused"` DiskQuota string `json:"diskquota"` HumandiskUsed string `json:"humandiskused"` HumandiskQuota string `json:"humandiskquota"` } // UAPIResponse is the generic UAPI response structure type UAPIResponse struct { APIVersion int `json:"apiversion"` Func string `json:"func"` Module string `json:"module"` Result *json.RawMessage `json:"result"` Status int `json:"status"` Errors []string `json:"errors"` Messages []string `json:"messages"` } // ListEmailsResult is the result from list_pops type ListEmailsResult struct { Data []EmailAccount `json:"data"` } // doRequest executes an HTTP request to cPanel UAPI func (c *Client) doRequest(module, function string, params map[string]string) ([]byte, error) { // Build UAPI URL apiURL := fmt.Sprintf("%s/execute/%s/%s", c.host, module, function) // Add query parameters if len(params) > 0 { values := url.Values{} for k, v := range params { values.Set(k, v) } apiURL += "?" + values.Encode() } req, err := http.NewRequest("GET", apiURL, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } // cPanel API Token authentication req.Header.Set("Authorization", fmt.Sprintf("cpanel %s:%s", c.username, c.apiToken)) resp, err := c.http.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode >= 400 { return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody)) } return respBody, nil } // ListEmails returns all email accounts for a domain func (c *Client) ListEmails(domain string) ([]EmailAccount, error) { params := map[string]string{} if domain != "" { params["domain"] = domain } respBody, err := c.doRequest("Email", "list_pops", params) if err != nil { return nil, err } var response UAPIResponse if err := json.Unmarshal(respBody, &response); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } if response.Status != 1 { if len(response.Errors) > 0 { return nil, fmt.Errorf("API error: %s", response.Errors[0]) } return nil, fmt.Errorf("unknown API error") } var result []EmailAccount if response.Result != nil { if err := json.Unmarshal(*response.Result, &result); err != nil { return nil, fmt.Errorf("failed to parse result: %w", err) } } return result, nil } // CreateEmail creates a new email account func (c *Client) CreateEmail(email, password string, quota int) error { // Parse email to get user and domain params := map[string]string{ "email": email, "password": password, "quota": fmt.Sprintf("%d", quota), // MB, 0 = unlimited } respBody, err := c.doRequest("Email", "add_pop", params) if err != nil { return err } var response UAPIResponse if err := json.Unmarshal(respBody, &response); err != nil { return fmt.Errorf("failed to parse response: %w", err) } if response.Status != 1 { if len(response.Errors) > 0 { return fmt.Errorf("API error: %s", response.Errors[0]) } return fmt.Errorf("failed to create email") } return nil } // DeleteEmail removes an email account func (c *Client) DeleteEmail(email string) error { params := map[string]string{ "email": email, } respBody, err := c.doRequest("Email", "delete_pop", params) if err != nil { return err } var response UAPIResponse if err := json.Unmarshal(respBody, &response); err != nil { return fmt.Errorf("failed to parse response: %w", err) } if response.Status != 1 { if len(response.Errors) > 0 { return fmt.Errorf("API error: %s", response.Errors[0]) } return fmt.Errorf("failed to delete email") } return nil } // ChangePassword changes an email account's password func (c *Client) ChangePassword(email, newPassword string) error { params := map[string]string{ "email": email, "password": newPassword, } respBody, err := c.doRequest("Email", "passwd_pop", params) if err != nil { return err } var response UAPIResponse if err := json.Unmarshal(respBody, &response); err != nil { return fmt.Errorf("failed to parse response: %w", err) } if response.Status != 1 { if len(response.Errors) > 0 { return fmt.Errorf("API error: %s", response.Errors[0]) } return fmt.Errorf("failed to change password") } return nil } // UpdateQuota updates an email account's disk quota func (c *Client) UpdateQuota(email string, quotaMB int) error { params := map[string]string{ "email": email, "quota": fmt.Sprintf("%d", quotaMB), } respBody, err := c.doRequest("Email", "edit_pop_quota", params) if err != nil { return err } var response UAPIResponse if err := json.Unmarshal(respBody, &response); err != nil { return fmt.Errorf("failed to parse response: %w", err) } if response.Status != 1 { if len(response.Errors) > 0 { return fmt.Errorf("API error: %s", response.Errors[0]) } return fmt.Errorf("failed to update quota") } return nil }