icbm.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. package handler
  2. import (
  3. "context"
  4. "github.com/mk6i/retro-aim-server/oscar"
  5. "github.com/mk6i/retro-aim-server/state"
  6. )
  7. const (
  8. evilDelta = uint16(100)
  9. evilDeltaAnon = uint16(30)
  10. )
  11. // NewICBMService returns a new instance of ICBMService.
  12. func NewICBMService(messageRelayer MessageRelayer, feedbagManager FeedbagManager) *ICBMService {
  13. return &ICBMService{messageRelayer: messageRelayer, feedbagManager: feedbagManager}
  14. }
  15. // ICBMService implements the ICBM food group, which is responsible for sending
  16. // and receiving instant messages and associated functionality such as warning,
  17. // typing events, etc.
  18. type ICBMService struct {
  19. messageRelayer MessageRelayer
  20. feedbagManager FeedbagManager
  21. }
  22. // ParameterQueryHandler returns ICBM service parameters.
  23. func (s ICBMService) ParameterQueryHandler(_ context.Context, inFrame oscar.SNACFrame) oscar.SNACMessage {
  24. return oscar.SNACMessage{
  25. Frame: oscar.SNACFrame{
  26. FoodGroup: oscar.ICBM,
  27. SubGroup: oscar.ICBMParameterReply,
  28. RequestID: inFrame.RequestID,
  29. },
  30. Body: oscar.SNAC_0x04_0x05_ICBMParameterReply{
  31. MaxSlots: 100,
  32. ICBMFlags: 3,
  33. MaxIncomingICBMLen: 512,
  34. MaxSourceEvil: 999,
  35. MaxDestinationEvil: 999,
  36. MinInterICBMInterval: 0,
  37. },
  38. }
  39. }
  40. // ChannelMsgToHostHandler relays the instant message SNAC
  41. // oscar.ICBMChannelMsgToHost from the sender to the intended recipient. It
  42. // returns oscar.ICBMHostAck if the oscar.ICBMChannelMsgToHost message contains
  43. // a request acknowledgement flag.
  44. func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.SNACMessage, error) {
  45. blocked, err := s.feedbagManager.BlockedState(sess.ScreenName(), inBody.ScreenName)
  46. if err != nil {
  47. return nil, err
  48. }
  49. if blocked != state.BlockedNo {
  50. code := oscar.ErrorCodeNotLoggedOn
  51. if blocked == state.BlockedA {
  52. code = oscar.ErrorCodeInLocalPermitDeny
  53. }
  54. return &oscar.SNACMessage{
  55. Frame: oscar.SNACFrame{
  56. FoodGroup: oscar.ICBM,
  57. SubGroup: oscar.ICBMErr,
  58. RequestID: inFrame.RequestID,
  59. },
  60. Body: oscar.SNACError{
  61. Code: code,
  62. },
  63. }, nil
  64. }
  65. recipSess := s.messageRelayer.RetrieveByScreenName(inBody.ScreenName)
  66. if recipSess == nil {
  67. return &oscar.SNACMessage{
  68. Frame: oscar.SNACFrame{
  69. FoodGroup: oscar.ICBM,
  70. SubGroup: oscar.ICBMErr,
  71. RequestID: inFrame.RequestID,
  72. },
  73. Body: oscar.SNACError{
  74. Code: oscar.ErrorCodeNotLoggedOn,
  75. },
  76. }, nil
  77. }
  78. clientIM := oscar.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  79. Cookie: inBody.Cookie,
  80. ChannelID: inBody.ChannelID,
  81. TLVUserInfo: oscar.TLVUserInfo{
  82. ScreenName: sess.ScreenName(),
  83. WarningLevel: sess.Warning(),
  84. },
  85. TLVRestBlock: oscar.TLVRestBlock{
  86. TLVList: oscar.TLVList{
  87. {
  88. Tag: 0x0B,
  89. Value: []byte{},
  90. },
  91. },
  92. },
  93. }
  94. // copy over TLVs from sender SNAC to recipient SNAC verbatim. this
  95. // includes ICBMTLVTagRequestHostAck, which is ignored by the client, as
  96. // far as I can tell.
  97. clientIM.AppendList(inBody.TLVRestBlock.TLVList)
  98. s.messageRelayer.RelayToScreenName(ctx, recipSess.ScreenName(), oscar.SNACMessage{
  99. Frame: oscar.SNACFrame{
  100. FoodGroup: oscar.ICBM,
  101. SubGroup: oscar.ICBMChannelMsgToClient,
  102. },
  103. Body: clientIM,
  104. })
  105. if _, requestedConfirmation := inBody.TLVRestBlock.Slice(oscar.ICBMTLVTagRequestHostAck); !requestedConfirmation {
  106. // don't ack message
  107. return nil, nil
  108. }
  109. // ack message back to sender
  110. return &oscar.SNACMessage{
  111. Frame: oscar.SNACFrame{
  112. FoodGroup: oscar.ICBM,
  113. SubGroup: oscar.ICBMHostAck,
  114. RequestID: inFrame.RequestID,
  115. },
  116. Body: oscar.SNAC_0x04_0x0C_ICBMHostAck{
  117. Cookie: inBody.Cookie,
  118. ChannelID: inBody.ChannelID,
  119. ScreenName: inBody.ScreenName,
  120. },
  121. }, nil
  122. }
  123. // ClientEventHandler relays SNAC oscar.ICBMClientEvent typing events from the
  124. // sender to the recipient.
  125. func (s ICBMService) ClientEventHandler(ctx context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
  126. blocked, err := s.feedbagManager.BlockedState(sess.ScreenName(), inBody.ScreenName)
  127. switch {
  128. case err != nil:
  129. return err
  130. case blocked != state.BlockedNo:
  131. return nil
  132. default:
  133. s.messageRelayer.RelayToScreenName(ctx, inBody.ScreenName, oscar.SNACMessage{
  134. Frame: oscar.SNACFrame{
  135. FoodGroup: oscar.ICBM,
  136. SubGroup: oscar.ICBMClientEvent,
  137. RequestID: inFrame.RequestID,
  138. },
  139. Body: oscar.SNAC_0x04_0x14_ICBMClientEvent{
  140. Cookie: inBody.Cookie,
  141. ChannelID: inBody.ChannelID,
  142. ScreenName: sess.ScreenName(),
  143. Event: inBody.Event,
  144. },
  145. })
  146. return nil
  147. }
  148. }
  149. // EvilRequestHandler handles user warning (a.k.a evil) notifications. It
  150. // receives oscar.ICBMEvilRequest warning SNAC, increments the warned user's
  151. // warning level, and sends the warned user a notification informing them that
  152. // they have been warned. The user may choose to warn anonymously or
  153. // non-anonymously. It returns SNAC oscar.ICBMEvilReply to confirm that the
  154. // warning was sent. Users may not warn themselves or warn users they have
  155. // blocked or are blocked by.
  156. func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.SNACMessage, error) {
  157. // don't let users warn themselves, it causes the AIM client to go into a
  158. // weird state.
  159. if inBody.ScreenName == sess.ScreenName() {
  160. return oscar.SNACMessage{
  161. Frame: oscar.SNACFrame{
  162. FoodGroup: oscar.ICBM,
  163. SubGroup: oscar.ICBMErr,
  164. RequestID: inFrame.RequestID,
  165. },
  166. Body: oscar.SNACError{
  167. Code: oscar.ErrorCodeNotSupportedByHost,
  168. },
  169. }, nil
  170. }
  171. blocked, err := s.feedbagManager.BlockedState(sess.ScreenName(), inBody.ScreenName)
  172. if err != nil {
  173. return oscar.SNACMessage{}, nil
  174. }
  175. if blocked != state.BlockedNo {
  176. return oscar.SNACMessage{
  177. Frame: oscar.SNACFrame{
  178. FoodGroup: oscar.ICBM,
  179. SubGroup: oscar.ICBMErr,
  180. RequestID: inFrame.RequestID,
  181. },
  182. Body: oscar.SNACError{
  183. Code: oscar.ErrorCodeNotLoggedOn,
  184. },
  185. }, nil
  186. }
  187. recipSess := s.messageRelayer.RetrieveByScreenName(inBody.ScreenName)
  188. if recipSess == nil {
  189. return oscar.SNACMessage{}, nil
  190. }
  191. increase := evilDelta
  192. if inBody.SendAs == 1 {
  193. increase = evilDeltaAnon
  194. }
  195. recipSess.IncrementWarning(increase)
  196. var notif any
  197. if inBody.SendAs == 0 {
  198. notif = oscar.SNAC_0x01_0x10_OServiceEvilNotification{
  199. NewEvil: recipSess.Warning(),
  200. TLVUserInfo: oscar.TLVUserInfo{
  201. ScreenName: sess.ScreenName(),
  202. WarningLevel: recipSess.Warning(),
  203. },
  204. }
  205. } else {
  206. notif = oscar.SNAC_0x01_0x10_OServiceEvilNotificationAnon{
  207. NewEvil: recipSess.Warning(),
  208. }
  209. }
  210. s.messageRelayer.RelayToScreenName(ctx, recipSess.ScreenName(), oscar.SNACMessage{
  211. Frame: oscar.SNACFrame{
  212. FoodGroup: oscar.OService,
  213. SubGroup: oscar.OServiceEvilNotification,
  214. },
  215. Body: notif,
  216. })
  217. // inform the warned user's buddies that their warning level has increased
  218. if err := broadcastArrival(ctx, recipSess, s.messageRelayer, s.feedbagManager); err != nil {
  219. return oscar.SNACMessage{}, nil
  220. }
  221. return oscar.SNACMessage{
  222. Frame: oscar.SNACFrame{
  223. FoodGroup: oscar.ICBM,
  224. SubGroup: oscar.ICBMEvilReply,
  225. RequestID: inFrame.RequestID,
  226. },
  227. Body: oscar.SNAC_0x04_0x09_ICBMEvilReply{
  228. EvilDeltaApplied: increase,
  229. UpdatedEvilValue: recipSess.Warning(),
  230. },
  231. }, nil
  232. }