diff --git a/AdcProtocol.go b/AdcProtocol.go index 5eb099a..7deef6c 100644 --- a/AdcProtocol.go +++ b/AdcProtocol.go @@ -1,6 +1,10 @@ package libnmdc import ( + "encoding/base32" + "fmt" + "regexp" + "strconv" "strings" ) @@ -15,12 +19,17 @@ const ( ) type AdcProtocol struct { - hc *HubConnection - state adcState - sid string - supports map[string]struct{} + hc *HubConnection + state adcState + sid, pid, cid string // all in base32 encoding + supports map[string]struct{} } +const ( + // extra extensions that aren't flagged in SUPPORTS + adcSeparateApVe string = "SEPARATE_AP_VE" // we invented this string +) + func NewAdcProtocol(hc *HubConnection) Protocol { proto := AdcProtocol{ hc: hc, @@ -28,12 +37,53 @@ func NewAdcProtocol(hc *HubConnection) Protocol { supports: make(map[string]struct{}), } + rxPid := regexp.MustCompile("^[A-Z2-7]{39}$") + if !rxPid.MatchString(hc.Hco.AdcPID) { + hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Invalid custom PID, regenerating"}) + hc.Hco.AdcPID = NewPID() + } + + pid_base32 := hc.Hco.AdcPID + cid_base32, err := proto.pid2cid(pid_base32) + if err != nil { + panic(err) + } + + proto.cid = cid_base32 + proto.pid = pid_base32 + // Start logging in hc.SayRaw("HSUP ADBASE ADTIGR\n") return &proto } +func (this *AdcProtocol) pid2cid(pid_base32 string) (string, error) { + + /* + Generate random data and store it in PID_raw, then; + PID = Base32( PID_raw ) + CID = Base32( Hash( PID_raw ) ) + + For GPA/PAS, assuming that '12345' is the random data supplied in GPA, then; + PAS = Base32( Hash( password + '12345' ) ) + */ + + pid_raw, err := base32.StdEncoding.DecodeString(pid_base32 + "=") + if err != nil { + return "", err + } + + cid_raw, err := TTH(string(pid_raw)) + if err != nil { + return "", err + } + + cid_base32 := Base32(cid_raw) + + return cid_base32, nil +} + func (this *AdcProtocol) ProcessCommand(msg string) { this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: msg}) @@ -91,7 +141,7 @@ func (this *AdcProtocol) ProcessCommand(msg string) { this.handleInfo(flags) // Transition to state VERIFY and send our own info - this.hc.SayRaw("BINF " + this.escape(this.sid) + " GARBAGE") // FIXME send a real info string + this.hc.SayRaw("BINF " + this.escape(this.sid) + " " + this.ourINFO(true) + "\n") this.state = adcStateVerify } else if this.state == adcStateNormal { @@ -103,6 +153,17 @@ func (this *AdcProtocol) ProcessCommand(msg string) { return } + case "ISTA": + // Message from the hub + if len(parts) < 3 { + this.malformed(parts) + return + } + + code, _ := strconv.Atoi(parts[1]) + msg := this.unescape(parts[2]) + this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: this.ErrorMessage(code, msg)}) + case "IGPA": // @@ -111,6 +172,62 @@ func (this *AdcProtocol) ProcessCommand(msg string) { } } +func (this *AdcProtocol) infoFlagsFor(u *UserInfo) map[string]string { + parts := map[string]string{ + "NI": u.Nick, + "SS": fmt.Sprintf("%d", u.ShareSize), + "SF": fmt.Sprintf("%d", u.SharedFiles), + "US": fmt.Sprintf("%d", u.UploadSpeedBps), + "DS": fmt.Sprintf("%d", u.DownloadSpeedBps), + "SL": fmt.Sprintf("%d", u.Slots), + "FS": fmt.Sprintf("%d", u.Slots), // Free slots - NOT IN ADC DOCUMENTATION + "HN": fmt.Sprintf("%d", u.HubsUnregistered), + "HR": fmt.Sprintf("%d", u.HubsRegistered), + "HO": fmt.Sprintf("%d", u.HubsOperator), + } + + if _, ok := this.supports[adcSeparateApVe]; ok { + parts["AP"] = u.ClientTag + parts["VE"] = u.ClientVersion + } else { + parts["VE"] = fmt.Sprintf("%s %s", u.ClientVersion, u.ClientTag) + } + + ct := 0 // 1=bot, 2=registered user, 4=operator, 8=super user, 16=hub owner, 32=hub + if u.IsBot { + ct |= 1 + } + if u.IsRegistered { + ct |= 2 + } + if u.IsOperator { + ct |= 4 + } + if u.IsSuperUser { + ct |= 8 + } + if u.IsHubOwner { + ct |= 16 + } + parts["CT"] = fmt.Sprintf("%d", ct) + + return parts +} + +func (this *AdcProtocol) ourINFO(includePid bool) string { + parts := this.infoFlagsFor(this.hc.Hco.Self) + parts["ID"] = this.cid + if includePid { + parts["PD"] = this.pid + } + + ret := "" + for k, v := range parts { + ret += " " + k + this.escape(v) + } + return ret[1:] +} + func (this *AdcProtocol) handleInfo(flags map[string]string) { if flags["CT"] == "32" { @@ -119,6 +236,12 @@ func (this *AdcProtocol) handleInfo(flags map[string]string) { // HI: // Hub properties updated + + // Special SUPPORT that is only indicated in IINF + if _, ok := flags["AP"]; ok { + this.supports[adcSeparateApVe] = struct{}{} + } + hubName, ok := flags["DE"] if ok { this.hc.HubName = this.unescape(hubName) @@ -164,3 +287,87 @@ func (this *AdcProtocol) SayPrivate(user, message string) { func (this *AdcProtocol) ProtoMessageSeparator() string { return "\n" } + +func (this *AdcProtocol) ErrorMessage(code int, msg string) string { + severity := code / 100 + category := (code % 100) / 10 + cat_sub := (code % 100) + + formatSeverity := func(severity int) string { + switch severity { + case 0: + return "OK" + case 1: + return "Warning" + case 2: + return "Error" + default: + return "" + } + } + + formatCategory := func(category int) string { + switch category { + case 0: + return "" + case 1: + return "Hub not accepting users" + case 2: + return "Login failed" + case 3: + return "Access denied" + case 4: + return "Protocol error" + case 5: + return "Transfer error" + default: + return "" + } + } + + formatCatSub := func(cat_sub int) string { + switch cat_sub { + case 11: + return "Hub is full" + case 12: + return "Hub is disabled" + case 21: + return "Invalid nick" + case 22: + return "Nick is already in use" + case 23: + return "Invalid password" + case 24: + return "CID already connected" + case 25: + return "Access denied" + case 26: + return "Registered users only" + case 27: + return "Invalid PID" + case 31: + return "Permanently banned" + case 32: + return "Temporarily banned" + default: + return "" + } + } + + parts := make([]string, 0, 4) + if fs := formatSeverity(severity); len(fs) > 0 { + parts = append(parts, fs) + } + if fc := formatCategory(category); len(fc) > 0 { + parts = append(parts, fc) + } + if fcs := formatCatSub(cat_sub); len(fcs) > 0 { + parts = append(parts, fcs) + } + if len(msg) > 0 { + parts = append(parts, msg) + } + + return strings.Join(parts, ": ") + fmt.Sprintf(" (code %d)", code) + +}