connection.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. package server
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "log"
  9. "log/slog"
  10. "net"
  11. "os"
  12. "github.com/google/uuid"
  13. "github.com/mkaminski/goaim/oscar"
  14. )
  15. var (
  16. CapChat, _ = uuid.MustParse("748F2420-6287-11D1-8222-444553540000").MarshalBinary()
  17. )
  18. var (
  19. ErrUnsupportedFoodGroup = errors.New("unimplemented food group, your client version may be unsupported")
  20. ErrUnsupportedSubGroup = errors.New("unimplemented subgroup, your client version may be unsupported")
  21. )
  22. type IncomingMessage struct {
  23. flap oscar.FlapFrame
  24. snac oscar.SnacFrame
  25. buf io.Reader
  26. }
  27. type XMessage struct {
  28. snacFrame oscar.SnacFrame
  29. snacOut any
  30. }
  31. func readIncomingRequests(ctx context.Context, logger *slog.Logger, rw io.Reader, msgCh chan IncomingMessage, errCh chan error) {
  32. defer close(msgCh)
  33. defer close(errCh)
  34. for {
  35. flap := oscar.FlapFrame{}
  36. if err := oscar.Unmarshal(&flap, rw); err != nil {
  37. errCh <- err
  38. return
  39. }
  40. switch flap.FrameType {
  41. case oscar.FlapFrameSignon:
  42. errCh <- errors.New("shouldn't get FlapFrameSignon")
  43. return
  44. case oscar.FlapFrameData:
  45. b := make([]byte, flap.PayloadLength)
  46. if _, err := rw.Read(b); err != nil {
  47. errCh <- err
  48. return
  49. }
  50. snac := oscar.SnacFrame{}
  51. buf := bytes.NewBuffer(b)
  52. if err := oscar.Unmarshal(&snac, buf); err != nil {
  53. errCh <- err
  54. return
  55. }
  56. msgCh <- IncomingMessage{
  57. flap: flap,
  58. snac: snac,
  59. buf: buf,
  60. }
  61. case oscar.FlapFrameError:
  62. errCh <- fmt.Errorf("got FlapFrameError: %v", flap)
  63. return
  64. case oscar.FlapFrameSignoff:
  65. errCh <- ErrSignedOff
  66. return
  67. case oscar.FlapFrameKeepAlive:
  68. logger.DebugContext(ctx, "keepalive heartbeat")
  69. default:
  70. errCh <- fmt.Errorf("unknown frame type: %v", flap)
  71. return
  72. }
  73. }
  74. }
  75. func Signout(ctx context.Context, logger *slog.Logger, sess *Session, sm SessionManager, fm *FeedbagStore) {
  76. if err := BroadcastDeparture(ctx, sess, sm, fm); err != nil {
  77. logger.ErrorContext(ctx, "error notifying departure", "err", err.Error())
  78. }
  79. sm.Remove(sess)
  80. }
  81. func ReadBos(ctx context.Context, cfg Config, sess *Session, seq uint32, sm SessionManager, fm *FeedbagStore, cr *ChatRegistry, rwc io.ReadWriter, room ChatRoom, router Router, logger *slog.Logger) {
  82. if err := router.WriteOServiceHostOnline(rwc, &seq); err != nil {
  83. logger.ErrorContext(ctx, "error WriteOServiceHostOnline")
  84. }
  85. // buffered so that the go routine has room to exit
  86. msgCh := make(chan IncomingMessage, 1)
  87. errCh := make(chan error, 1)
  88. go readIncomingRequests(ctx, logger, rwc, msgCh, errCh)
  89. rl := RouteLogger{
  90. Logger: logger,
  91. }
  92. for {
  93. select {
  94. case m := <-msgCh:
  95. if err := router.routeIncomingRequests(ctx, cfg, sm, sess, fm, cr, rwc, &seq, m.snac, m.buf, room); err != nil {
  96. if errors.Is(err, ErrUnsupportedSubGroup) || errors.Is(err, ErrUnsupportedFoodGroup) {
  97. if err1 := sendInvalidSNACErr(m.snac, rwc, &seq); err1 != nil {
  98. err = errors.Join(err1, err)
  99. }
  100. if cfg.FailFast {
  101. panic(err.Error())
  102. }
  103. }
  104. logRequestError(ctx, logger, m.snac, err)
  105. return
  106. }
  107. case m := <-sess.RecvMessage():
  108. if err := writeOutSNAC(oscar.SnacFrame{}, m.snacFrame, m.snacOut, &seq, rwc); err != nil {
  109. logRequestError(ctx, logger, m.snacFrame, err)
  110. return
  111. }
  112. rl.logRequest(ctx, m.snacFrame, m.snacOut)
  113. case <-sess.Closed():
  114. if err := gracefulDisconnect(seq, rwc); err != nil {
  115. logger.ErrorContext(ctx, "unable to gracefully disconnect user", "err", err)
  116. }
  117. return
  118. case err := <-errCh:
  119. switch {
  120. case errors.Is(io.EOF, err):
  121. fallthrough
  122. case errors.Is(ErrSignedOff, err):
  123. logger.InfoContext(ctx, "client signed off")
  124. default:
  125. logger.ErrorContext(ctx, "client disconnected with error", "err", err)
  126. }
  127. return
  128. }
  129. }
  130. }
  131. func logRequestError(ctx context.Context, logger *slog.Logger, inFrame oscar.SnacFrame, err error) {
  132. logger.LogAttrs(ctx, slog.LevelError, "client disconnected with error",
  133. slog.Group("request",
  134. slog.String("food_group", oscar.FoodGroupStr(inFrame.FoodGroup)),
  135. slog.String("sub_group", oscar.SubGroupStr(inFrame.FoodGroup, inFrame.SubGroup)),
  136. ),
  137. slog.String("err", err.Error()),
  138. )
  139. }
  140. func gracefulDisconnect(seq uint32, rwc io.ReadWriter) error {
  141. return oscar.Marshal(oscar.FlapFrame{
  142. StartMarker: 42,
  143. FrameType: oscar.FlapFrameSignoff,
  144. Sequence: uint16(seq),
  145. }, rwc)
  146. }
  147. func HandleChatConnection(ctx context.Context, cfg Config, fm *FeedbagStore, cr *ChatRegistry, conn net.Conn, router Router, logger *slog.Logger) {
  148. cookie, seq, err := VerifyChatLogin(conn)
  149. if err != nil {
  150. logger.ErrorContext(ctx, "user disconnected with error", "err", err.Error())
  151. return
  152. }
  153. room, err := cr.Retrieve(string(cookie.Cookie))
  154. if err != nil {
  155. logger.ErrorContext(ctx, "unable to find chat room", "err", err.Error())
  156. return
  157. }
  158. chatSess, found := room.Retrieve(cookie.SessID)
  159. if !found {
  160. logger.ErrorContext(ctx, "unable to find user for session", "sessID", cookie.SessID)
  161. return
  162. }
  163. defer chatSess.Close()
  164. go func() {
  165. <-chatSess.Closed()
  166. AlertUserLeft(ctx, chatSess, room)
  167. room.Remove(chatSess)
  168. cr.MaybeRemoveRoom(room.Cookie)
  169. conn.Close()
  170. }()
  171. ctx = context.WithValue(ctx, "screenName", chatSess.ScreenName)
  172. ReadBos(ctx, cfg, chatSess, seq, room.SessionManager, fm, cr, conn, room, router, logger)
  173. }
  174. func HandleAuthConnection(cfg Config, sm *InMemorySessionManager, fm *FeedbagStore, conn net.Conn) {
  175. defer conn.Close()
  176. seq := uint32(100)
  177. _, err := SendAndReceiveSignonFrame(conn, &seq)
  178. if err != nil {
  179. log.Println(err)
  180. return
  181. }
  182. err = ReceiveAndSendAuthChallenge(cfg, fm, conn, conn, &seq, uuid.New)
  183. if err != nil {
  184. log.Println(err)
  185. return
  186. }
  187. err = ReceiveAndSendBUCPLoginRequest(cfg, sm, fm, conn, conn, &seq, uuid.New)
  188. if err != nil {
  189. log.Println(err)
  190. return
  191. }
  192. }
  193. func HandleBOSConnection(ctx context.Context, cfg Config, sm *InMemorySessionManager, fm *FeedbagStore, cr *ChatRegistry, conn net.Conn, router Router, logger *slog.Logger) {
  194. sess, seq, err := VerifyLogin(sm, conn)
  195. if err != nil {
  196. logger.ErrorContext(ctx, "user disconnected with error", "err", err.Error())
  197. return
  198. }
  199. defer sess.Close()
  200. defer conn.Close()
  201. go func() {
  202. <-sess.Closed()
  203. Signout(ctx, logger, sess, sm, fm)
  204. }()
  205. ctx = context.WithValue(ctx, "screenName", sess.ScreenName)
  206. ReadBos(ctx, cfg, sess, seq, sm, fm, cr, conn, ChatRoom{}, router, logger)
  207. }
  208. func ListenChat(cfg Config, fm *FeedbagStore, cr *ChatRegistry, logger *slog.Logger) {
  209. addr := Address("", cfg.ChatPort)
  210. listener, err := net.Listen("tcp", addr)
  211. if err != nil {
  212. logger.Error("unable to bind chat server address", "err", err.Error())
  213. os.Exit(1)
  214. }
  215. defer listener.Close()
  216. logger.Info("starting service", "addr", addr)
  217. router := NewRouterForChat(logger)
  218. for {
  219. conn, err := listener.Accept()
  220. if err != nil {
  221. log.Println(err)
  222. continue
  223. }
  224. ctx := context.Background()
  225. ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
  226. logger.DebugContext(ctx, "accepted connection")
  227. go HandleChatConnection(ctx, cfg, fm, cr, conn, router, logger)
  228. }
  229. }
  230. func ListenBOS(cfg Config, sm *InMemorySessionManager, fm *FeedbagStore, cr *ChatRegistry, logger *slog.Logger) {
  231. addr := Address("", cfg.BOSPort)
  232. listener, err := net.Listen("tcp", addr)
  233. if err != nil {
  234. logger.Error("unable to bind BOS server address", "err", err.Error())
  235. os.Exit(1)
  236. }
  237. defer listener.Close()
  238. logger.Info("starting service", "addr", addr)
  239. router := NewRouter(logger)
  240. for {
  241. conn, err := listener.Accept()
  242. if err != nil {
  243. log.Println(err)
  244. continue
  245. }
  246. ctx := context.Background()
  247. ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
  248. logger.DebugContext(ctx, "accepted connection")
  249. go HandleBOSConnection(ctx, cfg, sm, fm, cr, conn, router, logger)
  250. }
  251. }
  252. func ListenBUCPLogin(cfg Config, err error, logger *slog.Logger, sm *InMemorySessionManager, fm *FeedbagStore) {
  253. addr := Address("", cfg.OSCARPort)
  254. listener, err := net.Listen("tcp", addr)
  255. if err != nil {
  256. logger.Error("unable to bind OSCAR server address", "err", err.Error())
  257. os.Exit(1)
  258. }
  259. defer listener.Close()
  260. logger.Info("starting OSCAR server", "addr", addr)
  261. for {
  262. conn, err := listener.Accept()
  263. if err != nil {
  264. log.Println(err)
  265. continue
  266. }
  267. go HandleAuthConnection(cfg, sm, fm, conn)
  268. }
  269. }