icbm.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. package handler
  2. import (
  3. "context"
  4. "github.com/mkaminski/goaim/oscar"
  5. "github.com/mkaminski/goaim/state"
  6. )
  7. const (
  8. evilDelta = uint16(100)
  9. evilDeltaAnon = uint16(30)
  10. )
  11. func NewICBMService(messageRelayer MessageRelayer, feedbagManager FeedbagManager) *ICBMService {
  12. return &ICBMService{messageRelayer: messageRelayer, feedbagManager: feedbagManager}
  13. }
  14. type ICBMService struct {
  15. messageRelayer MessageRelayer
  16. feedbagManager FeedbagManager
  17. }
  18. func (s ICBMService) ParameterQueryHandler(_ context.Context, inFrame oscar.SNACFrame) oscar.SNACMessage {
  19. return oscar.SNACMessage{
  20. Frame: oscar.SNACFrame{
  21. FoodGroup: oscar.ICBM,
  22. SubGroup: oscar.ICBMParameterReply,
  23. RequestID: inFrame.RequestID,
  24. },
  25. Body: oscar.SNAC_0x04_0x05_ICBMParameterReply{
  26. MaxSlots: 100,
  27. ICBMFlags: 3,
  28. MaxIncomingICBMLen: 512,
  29. MaxSourceEvil: 999,
  30. MaxDestinationEvil: 999,
  31. MinInterICBMInterval: 0,
  32. },
  33. }
  34. }
  35. func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.SNACMessage, error) {
  36. blocked, err := s.feedbagManager.Blocked(sess.ScreenName(), inBody.ScreenName)
  37. if err != nil {
  38. return nil, err
  39. }
  40. if blocked != state.BlockedNo {
  41. code := oscar.ErrorCodeNotLoggedOn
  42. if blocked == state.BlockedA {
  43. code = oscar.ErrorCodeInLocalPermitDeny
  44. }
  45. return &oscar.SNACMessage{
  46. Frame: oscar.SNACFrame{
  47. FoodGroup: oscar.ICBM,
  48. SubGroup: oscar.ICBMErr,
  49. RequestID: inFrame.RequestID,
  50. },
  51. Body: oscar.SNACError{
  52. Code: code,
  53. },
  54. }, nil
  55. }
  56. recipSess := s.messageRelayer.RetrieveByScreenName(inBody.ScreenName)
  57. if recipSess == nil {
  58. return &oscar.SNACMessage{
  59. Frame: oscar.SNACFrame{
  60. FoodGroup: oscar.ICBM,
  61. SubGroup: oscar.ICBMErr,
  62. RequestID: inFrame.RequestID,
  63. },
  64. Body: oscar.SNACError{
  65. Code: oscar.ErrorCodeNotLoggedOn,
  66. },
  67. }, nil
  68. }
  69. clientIM := oscar.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  70. Cookie: inBody.Cookie,
  71. ChannelID: inBody.ChannelID,
  72. TLVUserInfo: oscar.TLVUserInfo{
  73. ScreenName: sess.ScreenName(),
  74. WarningLevel: sess.Warning(),
  75. },
  76. TLVRestBlock: oscar.TLVRestBlock{
  77. TLVList: oscar.TLVList{
  78. {
  79. TType: 0x0B,
  80. Val: []byte{},
  81. },
  82. },
  83. },
  84. }
  85. // copy over TLVs from sender SNAC to recipient SNAC verbatim. this
  86. // includes ICBMTLVTagRequestHostAck, which is ignored by the client, as
  87. // far as I can tell.
  88. clientIM.AddTLVList(inBody.TLVRestBlock.TLVList)
  89. s.messageRelayer.SendToScreenName(ctx, recipSess.ScreenName(), oscar.SNACMessage{
  90. Frame: oscar.SNACFrame{
  91. FoodGroup: oscar.ICBM,
  92. SubGroup: oscar.ICBMChannelMsgToClient,
  93. },
  94. Body: clientIM,
  95. })
  96. if _, requestedConfirmation := inBody.TLVRestBlock.GetSlice(oscar.ICBMTLVTagRequestHostAck); !requestedConfirmation {
  97. // don't ack message
  98. return nil, nil
  99. }
  100. // ack message back to sender
  101. return &oscar.SNACMessage{
  102. Frame: oscar.SNACFrame{
  103. FoodGroup: oscar.ICBM,
  104. SubGroup: oscar.ICBMHostAck,
  105. RequestID: inFrame.RequestID,
  106. },
  107. Body: oscar.SNAC_0x04_0x0C_ICBMHostAck{
  108. Cookie: inBody.Cookie,
  109. ChannelID: inBody.ChannelID,
  110. ScreenName: inBody.ScreenName,
  111. },
  112. }, nil
  113. }
  114. func (s ICBMService) ClientEventHandler(ctx context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
  115. blocked, err := s.feedbagManager.Blocked(sess.ScreenName(), inBody.ScreenName)
  116. switch {
  117. case err != nil:
  118. return err
  119. case blocked != state.BlockedNo:
  120. return nil
  121. default:
  122. s.messageRelayer.SendToScreenName(ctx, inBody.ScreenName, oscar.SNACMessage{
  123. Frame: oscar.SNACFrame{
  124. FoodGroup: oscar.ICBM,
  125. SubGroup: oscar.ICBMClientEvent,
  126. RequestID: inFrame.RequestID,
  127. },
  128. Body: oscar.SNAC_0x04_0x14_ICBMClientEvent{
  129. Cookie: inBody.Cookie,
  130. ChannelID: inBody.ChannelID,
  131. ScreenName: sess.ScreenName(),
  132. Event: inBody.Event,
  133. },
  134. })
  135. return nil
  136. }
  137. }
  138. func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.SNACMessage, error) {
  139. // don't let users warn themselves, it causes the AIM client to go into a
  140. // weird state.
  141. if inBody.ScreenName == sess.ScreenName() {
  142. return oscar.SNACMessage{
  143. Frame: oscar.SNACFrame{
  144. FoodGroup: oscar.ICBM,
  145. SubGroup: oscar.ICBMErr,
  146. RequestID: inFrame.RequestID,
  147. },
  148. Body: oscar.SNACError{
  149. Code: oscar.ErrorCodeNotSupportedByHost,
  150. },
  151. }, nil
  152. }
  153. blocked, err := s.feedbagManager.Blocked(sess.ScreenName(), inBody.ScreenName)
  154. if err != nil {
  155. return oscar.SNACMessage{}, nil
  156. }
  157. if blocked != state.BlockedNo {
  158. return oscar.SNACMessage{
  159. Frame: oscar.SNACFrame{
  160. FoodGroup: oscar.ICBM,
  161. SubGroup: oscar.ICBMErr,
  162. RequestID: inFrame.RequestID,
  163. },
  164. Body: oscar.SNACError{
  165. Code: oscar.ErrorCodeNotLoggedOn,
  166. },
  167. }, nil
  168. }
  169. recipSess := s.messageRelayer.RetrieveByScreenName(inBody.ScreenName)
  170. if recipSess == nil {
  171. return oscar.SNACMessage{}, nil
  172. }
  173. increase := evilDelta
  174. if inBody.SendAs == 1 {
  175. increase = evilDeltaAnon
  176. }
  177. recipSess.IncreaseWarning(increase)
  178. var notif any
  179. if inBody.SendAs == 0 {
  180. notif = oscar.SNAC_0x01_0x10_OServiceEvilNotification{
  181. NewEvil: recipSess.Warning(),
  182. TLVUserInfo: oscar.TLVUserInfo{
  183. ScreenName: sess.ScreenName(),
  184. WarningLevel: recipSess.Warning(),
  185. },
  186. }
  187. } else {
  188. notif = oscar.SNAC_0x01_0x10_OServiceEvilNotificationAnon{
  189. NewEvil: recipSess.Warning(),
  190. }
  191. }
  192. s.messageRelayer.SendToScreenName(ctx, recipSess.ScreenName(), oscar.SNACMessage{
  193. Frame: oscar.SNACFrame{
  194. FoodGroup: oscar.OService,
  195. SubGroup: oscar.OServiceEvilNotification,
  196. },
  197. Body: notif,
  198. })
  199. if err := broadcastArrival(ctx, recipSess, s.messageRelayer, s.feedbagManager); err != nil {
  200. return oscar.SNACMessage{}, nil
  201. }
  202. return oscar.SNACMessage{
  203. Frame: oscar.SNACFrame{
  204. FoodGroup: oscar.ICBM,
  205. SubGroup: oscar.ICBMEvilReply,
  206. RequestID: inFrame.RequestID,
  207. },
  208. Body: oscar.SNAC_0x04_0x09_ICBMEvilReply{
  209. EvilDeltaApplied: increase,
  210. UpdatedEvilValue: recipSess.Warning(),
  211. },
  212. }, nil
  213. }