hswaw/smsgw: implement

The SMS gateway service allows consumers to subscribe to SMS messages
received by a Twilio phone number.

This is useful for receiving SMS auth messages.

Change-Id: Ib02a4306ad0d856dd10c7ca9241d9163809e7084
diff --git a/hswaw/smsgw/twilio.go b/hswaw/smsgw/twilio.go
new file mode 100644
index 0000000..cdc0255
--- /dev/null
+++ b/hswaw/smsgw/twilio.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+type twilio struct {
+	accountSID   string
+	accountToken string
+}
+
+type incomingPhoneNumber struct {
+	FriendlyName string `json:"friendly_name"`
+	SMSMethod    string `json:"sms_method"`
+	SMSURL       string `json:"sms_url"`
+	SID          string `json:"sid"`
+}
+
+func (t *twilio) getIncomingPhoneNumbers(ctx context.Context) ([]incomingPhoneNumber, error) {
+	url := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/IncomingPhoneNumbers.json", t.accountSID)
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	req.SetBasicAuth(t.accountSID, t.accountToken)
+	res, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer res.Body.Close()
+
+	result := struct {
+		Message string                `json:"message"`
+		Status  int64                 `json:"status"`
+		IPN     []incomingPhoneNumber `json:"incoming_phone_numbers"`
+	}{}
+
+	if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
+		return nil, err
+	}
+
+	if result.Message != "" {
+		return nil, fmt.Errorf("REST response error, status: %v, message: %q", result.Status, result.Message)
+	}
+
+	return result.IPN, nil
+}
+
+func (t *twilio) updateIncomingPhoneNumberSMSWebhook(ctx context.Context, sid, method, whurl string) error {
+	turl := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/IncomingPhoneNumbers/%s.json", t.accountSID, sid)
+
+	data := url.Values{}
+	data.Set("SmsMethod", method)
+	data.Set("SmsUrl", whurl)
+
+	req, err := http.NewRequest("POST", turl, strings.NewReader(data.Encode()))
+	if err != nil {
+		return err
+	}
+	req = req.WithContext(ctx)
+	req.SetBasicAuth(t.accountSID, t.accountToken)
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+	res, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return err
+	}
+	defer res.Body.Close()
+
+	if res.StatusCode != 200 {
+		return fmt.Errorf("status code: %v", res.StatusCode)
+	}
+	return nil
+}