github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/providers/ovh/protocol.go (about) 1 package ovh 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/StackExchange/dnscontrol/v2/models" 8 "github.com/miekg/dns/dnsutil" 9 ) 10 11 // Void an empty structure. 12 type Void struct { 13 } 14 15 // fetchDomainList gets list of zones for account 16 func (c *ovhProvider) fetchZones() error { 17 if c.zones != nil { 18 return nil 19 } 20 c.zones = map[string]bool{} 21 22 var response []string 23 24 err := c.client.CallAPI("GET", "/domain/zone", nil, &response, true) 25 26 if err != nil { 27 return err 28 } 29 30 for _, d := range response { 31 c.zones[d] = true 32 } 33 return nil 34 } 35 36 // Zone describes the attributes of a DNS zone. 37 type Zone struct { 38 DNSSecSupported bool `json:"dnssecSupported"` 39 HasDNSAnycast bool `json:"hasDNSAnycast,omitempty"` 40 NameServers []string `json:"nameServers"` 41 LastUpdate string `json:"lastUpdate,omitempty"` 42 } 43 44 // get info about a zone. 45 func (c *ovhProvider) fetchZone(fqdn string) (*Zone, error) { 46 var response Zone 47 48 err := c.client.CallAPI("GET", "/domain/zone/"+fqdn, nil, &response, true) 49 if err != nil { 50 return nil, err 51 } 52 53 return &response, nil 54 } 55 56 // Record describes a DNS record. 57 type Record struct { 58 Target string `json:"target,omitempty"` 59 Zone string `json:"zone,omitempty"` 60 TTL uint32 `json:"ttl,omitempty"` 61 FieldType string `json:"fieldType,omitempty"` 62 ID int64 `json:"id,omitempty"` 63 SubDomain string `json:"subDomain,omitempty"` 64 } 65 66 type records struct { 67 recordsID []int 68 } 69 70 func (c *ovhProvider) fetchRecords(fqdn string) ([]*Record, error) { 71 var recordIds []int 72 73 err := c.client.CallAPI("GET", "/domain/zone/"+fqdn+"/record", nil, &recordIds, true) 74 if err != nil { 75 return nil, err 76 } 77 78 records := make([]*Record, len(recordIds)) 79 for i, id := range recordIds { 80 r, err := c.fetchRecord(fqdn, id) 81 if err != nil { 82 return nil, err 83 } 84 records[i] = r 85 } 86 87 return records, nil 88 } 89 90 func (c *ovhProvider) fetchRecord(fqdn string, id int) (*Record, error) { 91 var response Record 92 93 err := c.client.CallAPI("GET", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, id), nil, &response, true) 94 if err != nil { 95 return nil, err 96 } 97 return &response, nil 98 } 99 100 // Returns a function that can be invoked to delete a record in a zone. 101 func (c *ovhProvider) deleteRecordFunc(id int64, fqdn string) func() error { 102 return func() error { 103 err := c.client.CallAPI("DELETE", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, id), nil, nil, true) 104 if err != nil { 105 return err 106 } 107 return nil 108 } 109 } 110 111 // Returns a function that can be invoked to create a record in a zone. 112 func (c *ovhProvider) createRecordFunc(rc *models.RecordConfig, fqdn string) func() error { 113 return func() error { 114 if c.isDKIMRecord(rc) { 115 rc.Type = "DKIM" 116 } 117 record := Record{ 118 SubDomain: dnsutil.TrimDomainName(rc.GetLabelFQDN(), fqdn), 119 FieldType: rc.Type, 120 Target: rc.GetTargetCombined(), 121 TTL: rc.TTL, 122 } 123 if record.SubDomain == "@" { 124 record.SubDomain = "" 125 } 126 var response Record 127 err := c.client.CallAPI("POST", fmt.Sprintf("/domain/zone/%s/record", fqdn), &record, &response, true) 128 return err 129 } 130 } 131 132 // Returns a function that can be invoked to update a record in a zone. 133 func (c *ovhProvider) updateRecordFunc(old *Record, rc *models.RecordConfig, fqdn string) func() error { 134 return func() error { 135 if c.isDKIMRecord(rc) { 136 rc.Type = "DKIM" 137 } 138 record := Record{ 139 SubDomain: rc.GetLabel(), 140 FieldType: rc.Type, 141 Target: rc.GetTargetCombined(), 142 TTL: rc.TTL, 143 Zone: fqdn, 144 ID: old.ID, 145 } 146 if record.SubDomain == "@" { 147 record.SubDomain = "" 148 } 149 150 err := c.client.CallAPI("PUT", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, old.ID), &record, &Void{}, true) 151 if err != nil && rc.Type == "DKIM" && strings.Contains(err.Error(), "alter read-only properties: fieldType") { 152 err = fmt.Errorf("This usually occurs when DKIM value is longer than the TXT record limit what OVH allows. Delete the TXT record to get past this limitation. [Original error: %s]", err.Error()) 153 } 154 155 return err 156 } 157 } 158 159 // Check if provided record is DKIM 160 func (c *ovhProvider) isDKIMRecord(rc *models.RecordConfig) bool { 161 return (rc != nil && rc.Type == "TXT" && strings.Contains(rc.GetLabel(), "._domainkey")) 162 } 163 164 func (c *ovhProvider) refreshZone(fqdn string) error { 165 return c.client.CallAPI("POST", fmt.Sprintf("/domain/zone/%s/refresh", fqdn), nil, &Void{}, true) 166 } 167 168 // fetch the NS OVH attributed to this zone (which is distinct from fetchRealNS which 169 // get the exact NS stored at the registrar 170 func (c *ovhProvider) fetchNS(fqdn string) ([]string, error) { 171 zone, err := c.fetchZone(fqdn) 172 if err != nil { 173 return nil, err 174 } 175 176 return zone.NameServers, nil 177 } 178 179 // CurrentNameServer stores information about nameservers. 180 type CurrentNameServer struct { 181 ToDelete bool `json:"toDelete,omitempty"` 182 IP string `json:"ip,omitempty"` 183 IsUsed bool `json:"isUsed,omitempty"` 184 ID int `json:"id,omitempty"` 185 Host string `json:"host,omitempty"` 186 } 187 188 // Retrieve the NS currently being deployed to the registrar 189 func (c *ovhProvider) fetchRegistrarNS(fqdn string) ([]string, error) { 190 var nameServersID []int 191 err := c.client.CallAPI("GET", "/domain/"+fqdn+"/nameServer", nil, &nameServersID, true) 192 if err != nil { 193 return nil, err 194 } 195 196 var nameServers []string 197 for _, id := range nameServersID { 198 var ns CurrentNameServer 199 err = c.client.CallAPI("GET", fmt.Sprintf("/domain/%s/nameServer/%d", fqdn, id), nil, &ns, true) 200 if err != nil { 201 return nil, err 202 } 203 204 // skip NS that we asked for deletion 205 if ns.ToDelete { 206 continue 207 } 208 nameServers = append(nameServers, ns.Host) 209 } 210 211 return nameServers, nil 212 } 213 214 // DomainNS describes a domain's NS in ovh's protocol. 215 type DomainNS struct { 216 Host string `json:"host,omitempty"` 217 IP string `json:"ip,omitempty"` 218 } 219 220 // UpdateNS describes a list of nameservers in ovh's protocol. 221 type UpdateNS struct { 222 NameServers []DomainNS `json:"nameServers"` 223 } 224 225 // Task describes a task in ovh's protocol. 226 type Task struct { 227 Function string `json:"function,omitempty"` 228 Status string `json:"status,omitempty"` 229 CanAccelerate bool `json:"canAccelerate,omitempty"` 230 LastUpdate string `json:"lastUpdate,omitempty"` 231 CreationDate string `json:"creationDate,omitempty"` 232 Comment string `json:"comment,omitempty"` 233 TodoDate string `json:"todoDate,omitempty"` 234 ID int64 `json:"id,omitempty"` 235 CanCancel bool `json:"canCancel,omitempty"` 236 DoneDate string `json:"doneDate,omitempty"` 237 CanRelaunch bool `json:"canRelaunch,omitempty"` 238 } 239 240 // Domain describes a domain in ovh's protocol. 241 type Domain struct { 242 NameServerType string `json:"nameServerType,omitempty"` 243 TransferLockStatus string `json:"transferLockStatus,omitempty"` 244 } 245 246 func (c *ovhProvider) updateNS(fqdn string, ns []string) error { 247 // we first need to make sure we can edit the NS 248 // by default zones are in "hosted" mode meaning they default 249 // to OVH default NS. In this mode, the NS can't be updated. 250 domain := Domain{NameServerType: "external"} 251 err := c.client.CallAPI("PUT", fmt.Sprintf("/domain/%s", fqdn), &domain, &Void{}, true) 252 if err != nil { 253 return err 254 } 255 256 var newNs []DomainNS 257 for _, n := range ns { 258 newNs = append(newNs, DomainNS{ 259 Host: n, 260 }) 261 } 262 263 update := UpdateNS{ 264 NameServers: newNs, 265 } 266 var task Task 267 err = c.client.CallAPI("POST", fmt.Sprintf("/domain/%s/nameServers/update", fqdn), &update, &task, true) 268 if err != nil { 269 return err 270 } 271 272 if task.Status == "error" { 273 return fmt.Errorf("API error while updating ns for %s: %s", fqdn, task.Comment) 274 } 275 276 // we don't wait for the task execution. One of the reason is that 277 // NS modification can take time in the registrar, the other is that every task 278 // in OVH is usually executed a few minutes after they have been registered. 279 // We count on the fact that `GetNameservers` uses the registrar API to get 280 // a coherent view (including pending modifications) of the registered NS. 281 282 return nil 283 }