{"record":{"id":"b55996102c31ffca","repo":"semaphoreui/semaphore","slug":"unexpected-server-challenge","errorCode":null,"errorMessage":"unexpected server challenge","messagePattern":"unexpected server challenge","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/mailer/auth.go","lineNumber":55,"sourceCode":"\t}\n\tif !slices.Contains(server.Auth, \"PLAIN\") {\n\t\ta.authMethod = \"LOGIN\"\n\t\treturn a.authMethod, nil, nil\n\t} else {\n\t\ta.authMethod = \"PLAIN\"\n\t\tresp := []byte(\"\\x00\" + a.username + \"\\x00\" + a.password)\n\t\treturn a.authMethod, resp, nil\n\t}\n}\n\nfunc (a *plainOrLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {\n\tif !more {\n\t\treturn nil, nil\n\t}\n\n\tif a.authMethod == \"PLAIN\" {\n\t\t// We've already sent everything.\n\t\treturn nil, errors.New(\"unexpected server challenge\")\n\t}\n\n\tswitch {\n\tcase bytes.Equal(fromServer, []byte(\"Username:\")):\n\t\treturn []byte(a.username), nil\n\tcase bytes.Equal(fromServer, []byte(\"Password:\")):\n\t\treturn []byte(a.password), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected server challenge: %s\", fromServer)\n\t}\n}\n","sourceCodeStart":37,"sourceCodeEnd":67,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/util/mailer/auth.go#L37-L67","documentation":"With authMethod \"PLAIN\", the client sends username and password in a single initial response, so no further server challenge is expected. If the server sends another challenge during Next, the state machine cannot continue and this error is returned. It usually indicates the server does not actually support PLAIN as advertised.","triggerScenarios":"Server advertises PLAIN in its AUTH mechanisms (so plainAuth is selected) but then issues an additional challenge instead of accepting the one-shot response - misbehaving or misconfigured SMTP server.","commonSituations":"Relay advertising PLAIN but implemented as LOGIN-only; middleware/proxy mangling the AUTH exchange; server state inconsistent after STARTTLS.","solutions":["Force the LOGIN mechanism (remove PLAIN from the server's advertised mechanisms or configure the client to prefer LOGIN).","Fix or replace the SMTP server/proxy so it honors RFC 4616 PLAIN semantics (single message, no follow-up challenge).","Capture the SMTP session and verify which AUTH mechanisms the server actually advertises after STARTTLS."],"exampleFix":"// before\n// server advertises [\"PLAIN\",\"LOGIN\"] but only implements LOGIN\n// after (client side preference)\nif !slices.Contains(server.Auth, \"PLAIN\") || serverBrokenPLAIN {\n    a.authMethod = \"LOGIN\" // use LOGIN flow for challenge/response\n}","handlingStrategy":"try-catch","validationCode":"// verify the server truly supports PLAIN before selecting it\nif !slices.Contains(serverInfo.Auth, \"PLAIN\") {\n    return errors.New(\"server does not advertise PLAIN; use LOGIN instead\")\n}","typeGuard":null,"tryCatchPattern":"_, _, err := auth.Next(fromServer, true)\nif err != nil && errors.Is(err, errUnexpectedChallenge) || err.Error() == \"unexpected server challenge\" {\n    // fall back to LOGIN mechanism and restart AUTH\n    return restartAuthWithLogin(client, username, password)\n}","preventionTips":["Verify the SMTP server's advertised AUTH mechanisms after STARTTLS, not just its config.","Prefer PLAIN only over verified TLS with servers known to implement RFC 4616 one-shot semantics.","Remove PLAIN from servers that only implement LOGIN to avoid mechanism mismatch.","Capture SMTP session traces when debugging third-party relay auth."],"tags":["smtp","auth","plain","protocol"],"backgroundTag":"unexpected-response-shape","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}