You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

chanserv.go 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. // Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "fmt"
  6. "sort"
  7. "strings"
  8. "time"
  9. "github.com/goshuirc/irc-go/ircfmt"
  10. "github.com/oragono/oragono/irc/modes"
  11. "github.com/oragono/oragono/irc/sno"
  12. "github.com/oragono/oragono/irc/utils"
  13. )
  14. const chanservHelp = `ChanServ lets you register and manage channels.`
  15. const chanservMask = "ChanServ!ChanServ@localhost"
  16. func chanregEnabled(config *Config) bool {
  17. return config.Channels.Registration.Enabled
  18. }
  19. var (
  20. chanservCommands = map[string]*serviceCommand{
  21. "op": {
  22. handler: csOpHandler,
  23. help: `Syntax: $bOP #channel [nickname]$b
  24. OP makes the given nickname, or yourself, a channel admin. You can only use
  25. this command if you're the founder of the channel.`,
  26. helpShort: `$bOP$b makes the given user (or yourself) a channel admin.`,
  27. authRequired: true,
  28. enabled: chanregEnabled,
  29. minParams: 1,
  30. },
  31. "register": {
  32. handler: csRegisterHandler,
  33. help: `Syntax: $bREGISTER #channel$b
  34. REGISTER lets you own the given channel. If you rejoin this channel, you'll be
  35. given admin privs on it. Modes set on the channel and the topic will also be
  36. remembered.`,
  37. helpShort: `$bREGISTER$b lets you own a given channel.`,
  38. authRequired: true,
  39. enabled: chanregEnabled,
  40. minParams: 1,
  41. },
  42. "unregister": {
  43. handler: csUnregisterHandler,
  44. help: `Syntax: $bUNREGISTER #channel [code]$b
  45. UNREGISTER deletes a channel registration, allowing someone else to claim it.
  46. To prevent accidental unregistrations, a verification code is required;
  47. invoking the command without a code will display the necessary code.`,
  48. helpShort: `$bUNREGISTER$b deletes a channel registration.`,
  49. enabled: chanregEnabled,
  50. minParams: 1,
  51. },
  52. "drop": {
  53. aliasOf: "unregister",
  54. },
  55. "amode": {
  56. handler: csAmodeHandler,
  57. help: `Syntax: $bAMODE #channel [mode change] [account]$b
  58. AMODE lists or modifies persistent mode settings that affect channel members.
  59. For example, $bAMODE #channel +o dan$b grants the the holder of the "dan"
  60. account the +o operator mode every time they join #channel. To list current
  61. accounts and modes, use $bAMODE #channel$b. Note that users are always
  62. referenced by their registered account names, not their nicknames.`,
  63. helpShort: `$bAMODE$b modifies persistent mode settings for channel members.`,
  64. enabled: chanregEnabled,
  65. minParams: 1,
  66. },
  67. "clear": {
  68. handler: csClearHandler,
  69. help: `Syntax: $bCLEAR #channel target$b
  70. CLEAR removes users or settings from a channel. Specifically:
  71. $bCLEAR #channel users$b kicks all users except for you.
  72. $bCLEAR #channel access$b resets all stored bans, invites, ban exceptions,
  73. and persistent user-mode grants made with CS AMODE.`,
  74. helpShort: `$bCLEAR$b removes users or settings from a channel.`,
  75. enabled: chanregEnabled,
  76. minParams: 2,
  77. },
  78. "transfer": {
  79. handler: csTransferHandler,
  80. help: `Syntax: $bTRANSFER [accept] #channel user [code]$b
  81. TRANSFER transfers ownership of a channel from one user to another.
  82. To prevent accidental transfers, a verification code is required. For
  83. example, $bTRANSFER #channel alice$b displays the required confirmation
  84. code, then $bTRANSFER #channel alice 2930242125$b initiates the transfer.
  85. Unless you are an IRC operator with the correct permissions, alice must
  86. then accept the transfer, which she can do with $bTRANSFER accept #channel$b.
  87. To cancel a pending transfer, transfer the channel to yourself.`,
  88. helpShort: `$bTRANSFER$b transfers ownership of a channel to another user.`,
  89. enabled: chanregEnabled,
  90. minParams: 2,
  91. },
  92. "purge": {
  93. handler: csPurgeHandler,
  94. help: `Syntax: $bPURGE #channel [reason]$b
  95. PURGE blacklists a channel from the server, making it impossible to join
  96. or otherwise interact with the channel. If the channel currently has members,
  97. they will be kicked from it. PURGE may also be applied preemptively to
  98. channels that do not currently have members.`,
  99. helpShort: `$bPURGE$b blacklists a channel from the server.`,
  100. capabs: []string{"chanreg"},
  101. minParams: 1,
  102. maxParams: 2,
  103. unsplitFinalParam: true,
  104. },
  105. "unpurge": {
  106. handler: csUnpurgeHandler,
  107. help: `Syntax: $bUNPURGE #channel$b
  108. UNPURGE removes any blacklisting of a channel that was previously
  109. set using PURGE.`,
  110. helpShort: `$bUNPURGE$b undoes a previous PURGE command.`,
  111. capabs: []string{"chanreg"},
  112. minParams: 1,
  113. },
  114. "info": {
  115. handler: csInfoHandler,
  116. help: `Syntax: $INFO #channel$b
  117. INFO displays info about a registered channel.`,
  118. helpShort: `$bINFO$b displays info about a registered channel.`,
  119. enabled: chanregEnabled,
  120. minParams: 1,
  121. },
  122. }
  123. )
  124. // csNotice sends the client a notice from ChanServ
  125. func csNotice(rb *ResponseBuffer, text string) {
  126. rb.Add(nil, chanservMask, "NOTICE", rb.target.Nick(), text)
  127. }
  128. func csAmodeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  129. channelName := params[0]
  130. channel := server.channels.Get(channelName)
  131. if channel == nil {
  132. csNotice(rb, client.t("Channel does not exist"))
  133. return
  134. } else if channel.Founder() == "" {
  135. csNotice(rb, client.t("Channel is not registered"))
  136. return
  137. }
  138. modeChanges, unknown := modes.ParseChannelModeChanges(params[1:]...)
  139. var change modes.ModeChange
  140. if len(modeChanges) > 1 || len(unknown) > 0 {
  141. csNotice(rb, client.t("Invalid mode change"))
  142. return
  143. } else if len(modeChanges) == 1 {
  144. change = modeChanges[0]
  145. } else {
  146. change = modes.ModeChange{Op: modes.List}
  147. }
  148. // normalize and validate the account argument
  149. accountIsValid := false
  150. change.Arg, _ = CasefoldName(change.Arg)
  151. switch change.Op {
  152. case modes.List:
  153. accountIsValid = true
  154. case modes.Add:
  155. // if we're adding a mode, the account must exist
  156. if change.Arg != "" {
  157. _, err := server.accounts.LoadAccount(change.Arg)
  158. accountIsValid = (err == nil)
  159. }
  160. case modes.Remove:
  161. // allow removal of accounts that may have been deleted
  162. accountIsValid = (change.Arg != "")
  163. }
  164. if !accountIsValid {
  165. csNotice(rb, client.t("Account does not exist"))
  166. return
  167. }
  168. affectedModes, err := channel.ProcessAccountToUmodeChange(client, change)
  169. if err == errInsufficientPrivs {
  170. csNotice(rb, client.t("Insufficient privileges"))
  171. return
  172. } else if err != nil {
  173. csNotice(rb, client.t("Internal error"))
  174. return
  175. }
  176. switch change.Op {
  177. case modes.List:
  178. // sort the persistent modes in descending order of priority
  179. sort.Slice(affectedModes, func(i, j int) bool {
  180. return umodeGreaterThan(affectedModes[i].Mode, affectedModes[j].Mode)
  181. })
  182. csNotice(rb, fmt.Sprintf(client.t("Channel %[1]s has %[2]d persistent modes set"), channelName, len(affectedModes)))
  183. for _, modeChange := range affectedModes {
  184. csNotice(rb, fmt.Sprintf(client.t("Account %[1]s receives mode +%[2]s"), modeChange.Arg, string(modeChange.Mode)))
  185. }
  186. case modes.Add, modes.Remove:
  187. if len(affectedModes) > 0 {
  188. csNotice(rb, fmt.Sprintf(client.t("Successfully set mode %s"), change.String()))
  189. } else {
  190. csNotice(rb, client.t("No changes were made"))
  191. }
  192. }
  193. }
  194. func csOpHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  195. channelInfo := server.channels.Get(params[0])
  196. if channelInfo == nil {
  197. csNotice(rb, client.t("Channel does not exist"))
  198. return
  199. }
  200. channelName := channelInfo.Name()
  201. clientAccount := client.Account()
  202. if clientAccount == "" || clientAccount != channelInfo.Founder() {
  203. csNotice(rb, client.t("Only the channel founder can do this"))
  204. return
  205. }
  206. var target *Client
  207. if len(params) > 1 {
  208. target = server.clients.Get(params[1])
  209. if target == nil {
  210. csNotice(rb, client.t("Could not find given client"))
  211. return
  212. }
  213. } else {
  214. target = client
  215. }
  216. // give them privs
  217. givenMode := modes.ChannelOperator
  218. if clientAccount == target.Account() {
  219. givenMode = modes.ChannelFounder
  220. }
  221. change := channelInfo.applyModeToMember(client, givenMode, modes.Add, target.NickCasefolded(), rb)
  222. if change != nil {
  223. //TODO(dan): we should change the name of String and make it return a slice here
  224. //TODO(dan): unify this code with code in modes.go
  225. args := append([]string{channelName}, strings.Split(change.String(), " ")...)
  226. for _, member := range channelInfo.Members() {
  227. member.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "MODE", args...)
  228. }
  229. }
  230. csNotice(rb, fmt.Sprintf(client.t("Successfully op'd in channel %s"), channelName))
  231. tnick := target.Nick()
  232. server.logger.Info("services", fmt.Sprintf("Client %s op'd [%s] in channel %s", client.Nick(), tnick, channelName))
  233. server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] CS OP'd $c[grey][$r%s$c[grey]] in channel $c[grey][$r%s$c[grey]]"), client.NickMaskString(), tnick, channelName))
  234. }
  235. func csRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  236. channelName := params[0]
  237. channelKey, err := CasefoldChannel(channelName)
  238. if err != nil {
  239. csNotice(rb, client.t("Channel name is not valid"))
  240. return
  241. }
  242. channelInfo := server.channels.Get(channelKey)
  243. if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) {
  244. csNotice(rb, client.t("You must be an oper on the channel to register it"))
  245. return
  246. }
  247. account := client.Account()
  248. if !checkChanLimit(client, rb) {
  249. return
  250. }
  251. // this provides the synchronization that allows exactly one registration of the channel:
  252. err = server.channels.SetRegistered(channelKey, account)
  253. if err != nil {
  254. csNotice(rb, err.Error())
  255. return
  256. }
  257. csNotice(rb, fmt.Sprintf(client.t("Channel %s successfully registered"), channelName))
  258. server.logger.Info("services", fmt.Sprintf("Client %s registered channel %s", client.Nick(), channelName))
  259. server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
  260. // give them founder privs
  261. change := channelInfo.applyModeToMember(client, modes.ChannelFounder, modes.Add, client.NickCasefolded(), rb)
  262. if change != nil {
  263. //TODO(dan): we should change the name of String and make it return a slice here
  264. //TODO(dan): unify this code with code in modes.go
  265. args := append([]string{channelName}, strings.Split(change.String(), " ")...)
  266. for _, member := range channelInfo.Members() {
  267. member.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "MODE", args...)
  268. }
  269. }
  270. }
  271. // check whether a client has already registered too many channels
  272. func checkChanLimit(client *Client, rb *ResponseBuffer) (ok bool) {
  273. account := client.Account()
  274. channelsAlreadyRegistered := client.server.accounts.ChannelsForAccount(account)
  275. ok = len(channelsAlreadyRegistered) < client.server.Config().Channels.Registration.MaxChannelsPerAccount || client.HasRoleCapabs("chanreg")
  276. if !ok {
  277. csNotice(rb, client.t("You have already registered the maximum number of channels; try dropping some with /CS UNREGISTER"))
  278. }
  279. return
  280. }
  281. func csUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  282. channelName := params[0]
  283. var verificationCode string
  284. if len(params) > 1 {
  285. verificationCode = params[1]
  286. }
  287. channelKey, err := CasefoldChannel(channelName)
  288. if channelKey == "" || err != nil {
  289. csNotice(rb, client.t("Channel name is not valid"))
  290. return
  291. }
  292. channel := server.channels.Get(channelKey)
  293. if channel == nil {
  294. csNotice(rb, client.t("No such channel"))
  295. return
  296. }
  297. founder := channel.Founder()
  298. if founder == "" {
  299. csNotice(rb, client.t("That channel is not registered"))
  300. return
  301. }
  302. hasPrivs := client.HasRoleCapabs("chanreg") || founder == client.Account()
  303. if !hasPrivs {
  304. csNotice(rb, client.t("Insufficient privileges"))
  305. return
  306. }
  307. info := channel.ExportRegistration(0)
  308. expectedCode := utils.ConfirmationCode(info.Name, info.RegisteredAt)
  309. if expectedCode != verificationCode {
  310. csNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b")))
  311. csNotice(rb, fmt.Sprintf(client.t("To confirm channel unregistration, type: /CS UNREGISTER %[1]s %[2]s"), channelKey, expectedCode))
  312. return
  313. }
  314. server.channels.SetUnregistered(channelKey, founder)
  315. csNotice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey))
  316. }
  317. func csClearHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  318. channel := server.channels.Get(params[0])
  319. if channel == nil {
  320. csNotice(rb, client.t("Channel does not exist"))
  321. return
  322. }
  323. account := client.Account()
  324. if !(client.HasRoleCapabs("chanreg") || (account != "" && account == channel.Founder())) {
  325. csNotice(rb, client.t("Insufficient privileges"))
  326. return
  327. }
  328. switch strings.ToLower(params[1]) {
  329. case "access":
  330. channel.resetAccess()
  331. csNotice(rb, client.t("Successfully reset channel access"))
  332. case "users":
  333. for _, target := range channel.Members() {
  334. if target != client {
  335. channel.Kick(client, target, "Cleared by ChanServ", rb, true)
  336. }
  337. }
  338. default:
  339. csNotice(rb, client.t("Invalid parameters"))
  340. }
  341. }
  342. func csTransferHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  343. if strings.ToLower(params[0]) == "accept" {
  344. processTransferAccept(client, params[1], rb)
  345. return
  346. }
  347. chname := params[0]
  348. channel := server.channels.Get(chname)
  349. if channel == nil {
  350. csNotice(rb, client.t("Channel does not exist"))
  351. return
  352. }
  353. regInfo := channel.ExportRegistration(0)
  354. chname = regInfo.Name
  355. account := client.Account()
  356. isFounder := account != "" && account == regInfo.Founder
  357. hasPrivs := client.HasRoleCapabs("chanreg")
  358. if !(isFounder || hasPrivs) {
  359. csNotice(rb, client.t("Insufficient privileges"))
  360. return
  361. }
  362. target := params[1]
  363. targetAccount, err := server.accounts.LoadAccount(params[1])
  364. if err != nil {
  365. csNotice(rb, client.t("Account does not exist"))
  366. return
  367. }
  368. if targetAccount.NameCasefolded != account {
  369. expectedCode := utils.ConfirmationCode(regInfo.Name, regInfo.RegisteredAt)
  370. codeValidated := 2 < len(params) && params[2] == expectedCode
  371. if !codeValidated {
  372. csNotice(rb, ircfmt.Unescape(client.t("$bWarning: you are about to transfer control of your channel to another user.$b")))
  373. csNotice(rb, fmt.Sprintf(client.t("To confirm your channel transfer, type: /CS TRANSFER %[1]s %[2]s %[3]s"), chname, target, expectedCode))
  374. return
  375. }
  376. }
  377. status, err := channel.Transfer(client, target, hasPrivs)
  378. if err == nil {
  379. switch status {
  380. case channelTransferComplete:
  381. csNotice(rb, fmt.Sprintf(client.t("Successfully transferred channel %[1]s to account %[2]s"), chname, target))
  382. case channelTransferPending:
  383. sendTransferPendingNotice(server, target, chname)
  384. csNotice(rb, fmt.Sprintf(client.t("Transfer of channel %[1]s to account %[2]s succeeded, pending acceptance"), chname, target))
  385. case channelTransferCancelled:
  386. csNotice(rb, fmt.Sprintf(client.t("Cancelled pending transfer of channel %s"), chname))
  387. }
  388. } else {
  389. csNotice(rb, client.t("Could not transfer channel"))
  390. }
  391. }
  392. func sendTransferPendingNotice(server *Server, account, chname string) {
  393. clients := server.accounts.AccountToClients(account)
  394. if len(clients) == 0 {
  395. return
  396. }
  397. var client *Client
  398. for _, candidate := range clients {
  399. client = candidate
  400. if candidate.NickCasefolded() == candidate.Account() {
  401. break // prefer the login where the nick is the account
  402. }
  403. }
  404. client.Send(nil, chanservMask, "NOTICE", client.Nick(), fmt.Sprintf(client.t("You have been offered ownership of channel %[1]s. To accept, /CS TRANSFER ACCEPT %[1]s"), chname))
  405. }
  406. func processTransferAccept(client *Client, chname string, rb *ResponseBuffer) {
  407. channel := client.server.channels.Get(chname)
  408. if channel == nil {
  409. csNotice(rb, client.t("Channel does not exist"))
  410. return
  411. }
  412. if !checkChanLimit(client, rb) {
  413. return
  414. }
  415. switch channel.AcceptTransfer(client) {
  416. case nil:
  417. csNotice(rb, fmt.Sprintf(client.t("Successfully accepted ownership of channel %s"), channel.Name()))
  418. case errChannelTransferNotOffered:
  419. csNotice(rb, fmt.Sprintf(client.t("You weren't offered ownership of channel %s"), channel.Name()))
  420. default:
  421. csNotice(rb, fmt.Sprintf(client.t("Could not accept ownership of channel %s"), channel.Name()))
  422. }
  423. }
  424. func csPurgeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  425. oper := client.Oper()
  426. if oper == nil {
  427. return // should be impossible because you need oper capabs for this
  428. }
  429. chname := params[0]
  430. var reason string
  431. if 1 < len(params) {
  432. reason = params[1]
  433. }
  434. purgeRecord := ChannelPurgeRecord{
  435. Oper: oper.Name,
  436. PurgedAt: time.Now().UTC(),
  437. Reason: reason,
  438. }
  439. switch server.channels.Purge(chname, purgeRecord) {
  440. case nil:
  441. channel := server.channels.Get(chname)
  442. if channel != nil { // channel need not exist to be purged
  443. for _, target := range channel.Members() {
  444. channel.Kick(client, target, "Cleared by ChanServ", rb, true)
  445. }
  446. }
  447. csNotice(rb, fmt.Sprintf(client.t("Successfully purged channel %s from the server"), chname))
  448. case errInvalidChannelName:
  449. csNotice(rb, fmt.Sprintf(client.t("Can't purge invalid channel %s"), chname))
  450. default:
  451. csNotice(rb, client.t("An error occurred"))
  452. }
  453. }
  454. func csUnpurgeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  455. chname := params[0]
  456. switch server.channels.Unpurge(chname) {
  457. case nil:
  458. csNotice(rb, fmt.Sprintf(client.t("Successfully unpurged channel %s from the server"), chname))
  459. case errNoSuchChannel:
  460. csNotice(rb, fmt.Sprintf(client.t("Channel %s wasn't previously purged from the server"), chname))
  461. default:
  462. csNotice(rb, client.t("An error occurred"))
  463. }
  464. }
  465. func csInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  466. chname, err := CasefoldChannel(params[0])
  467. if err != nil {
  468. csNotice(rb, client.t("Invalid channel name"))
  469. return
  470. }
  471. // purge status
  472. if client.HasRoleCapabs("chanreg") {
  473. purgeRecord, err := server.channelRegistry.LoadPurgeRecord(chname)
  474. if err == nil {
  475. csNotice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname))
  476. csNotice(rb, fmt.Sprintf(client.t("Purged by operator: %s"), purgeRecord.Oper))
  477. csNotice(rb, fmt.Sprintf(client.t("Purged at: %s"), purgeRecord.PurgedAt.Format(time.RFC1123)))
  478. if purgeRecord.Reason != "" {
  479. csNotice(rb, fmt.Sprintf(client.t("Purge reason: %s"), purgeRecord.Reason))
  480. }
  481. }
  482. } else {
  483. if server.channels.IsPurged(chname) {
  484. csNotice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname))
  485. }
  486. }
  487. var chinfo RegisteredChannel
  488. channel := server.channels.Get(params[0])
  489. if channel != nil {
  490. chinfo = channel.ExportRegistration(0)
  491. } else {
  492. chinfo, err = server.channelRegistry.LoadChannel(chname)
  493. if err != nil && !(err == errNoSuchChannel || err == errFeatureDisabled) {
  494. csNotice(rb, client.t("An error occurred"))
  495. return
  496. }
  497. }
  498. // channel exists but is unregistered, or doesn't exist:
  499. if chinfo.Founder == "" {
  500. csNotice(rb, fmt.Sprintf(client.t("Channel %s is not registered"), chname))
  501. return
  502. }
  503. csNotice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name))
  504. csNotice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder))
  505. csNotice(rb, fmt.Sprintf(client.t("Registered at: %s"), chinfo.RegisteredAt.Format(time.RFC1123)))
  506. }