Skip to content

Commit

Permalink
supermicro: support logins to older x11s
Browse files Browse the repository at this point in the history
The credentials passed in should not be base64 encoded
  • Loading branch information
joelrebel committed Jan 12, 2025
1 parent fffd096 commit 8023a68
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 22 deletions.
10 changes: 6 additions & 4 deletions providers/supermicro/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import (
)

var (
ErrQueryFRUInfo = errors.New("FRU information query returned error")
ErrXMLAPIUnsupported = errors.New("XML API is unsupported")
ErrModelUnknown = errors.New("Model number unknown")
ErrModelUnsupported = errors.New("Model not supported")
ErrQueryFRUInfo = errors.New("FRU information query returned error")
ErrXMLAPIUnsupported = errors.New("XML API is unsupported")
ErrModelUnknown = errors.New("Model number unknown")
ErrModelUnsupported = errors.New("Model not supported")
ErrUnexpectedResponse = errors.New("Unexpected response content")
ErrUnexpectedStatusCode = errors.New("Unexpected status code")
)

type UnexpectedResponseError struct {
Expand Down
44 changes: 35 additions & 9 deletions providers/supermicro/supermicro.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,34 +150,60 @@ func NewClient(host, user, pass string, log logr.Logger, opts ...Option) *Client
}
}

// Open a connection to a Supermicro BMC using the vendor API.
func (c *Client) Open(ctx context.Context) (err error) {
func (c *Client) login(ctx context.Context, encodeCreds bool) error {
var user, pass string
if encodeCreds {
user = base64.StdEncoding.EncodeToString([]byte(c.serviceClient.user))
pass = base64.StdEncoding.EncodeToString([]byte(c.serviceClient.pass))
} else {
user = c.serviceClient.user
pass = c.serviceClient.pass
}

data := fmt.Sprintf(
"name=%s&pwd=%s&check=00",
base64.StdEncoding.EncodeToString([]byte(c.serviceClient.user)),
base64.StdEncoding.EncodeToString([]byte(c.serviceClient.pass)),
user,
pass,
)

headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"}

body, status, err := c.serviceClient.query(ctx, "cgi/login.cgi", http.MethodPost, bytes.NewBufferString(data), headers, 0)
if err != nil {
return errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error())
return err
}

if status != 200 {
return errors.Wrap(bmclibErrs.ErrLoginFailed, strconv.Itoa(status))
return errors.Wrap(ErrUnexpectedStatusCode, strconv.Itoa(status))
}

// Older Supermicro boards return 200 for failed logins
if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=mainmenu`)) &&
!bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) {
return ErrUnexpectedResponse
}

return nil
}

// Open a connection to a Supermicro BMC using the vendor API.
func (c *Client) Open(ctx context.Context) (err error) {
// called after a session was opened but further login dependencies failed
closeWithError := func(ctx context.Context, err error) error {
_ = c.Close(ctx)
return err
}

if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=mainmenu`)) &&
!bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) {
return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, "unexpected response contents"))
// first attempt login with base64 encoded user,pass
if err := c.login(ctx, true); err != nil {
if !errors.Is(err, ErrUnexpectedResponse) && !errors.Is(err, ErrUnexpectedStatusCode) {
return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()))
}

// retry with plain text user, pass
if err2 := c.login(ctx, false); err2 != nil {
return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err2.Error()))
}
}

contentsTopMenu, status, err := c.serviceClient.query(ctx, "cgi/url_redirect.cgi?url_name=topmenu", http.MethodGet, nil, nil, 0)
Expand Down
10 changes: 1 addition & 9 deletions providers/supermicro/supermicro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,21 +187,13 @@ func TestOpen(t *testing.T) {
},
{
"login error",
"401: failed to login",
"failed to login",
"foo",
"bar",
handlerFuncMap{
"/cgi/login.cgi": func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, http.MethodPost)
assert.Equal(t, r.Header.Get("Content-Type"), "application/x-www-form-urlencoded")

b, err := io.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}

assert.Equal(t, `name=Zm9v&pwd=YmFy&check=00`, string(b))

response := []byte(`barf`)
w.WriteHeader(401)
_, _ = w.Write(response)
Expand Down

0 comments on commit 8023a68

Please sign in to comment.