<commit_before>//
// nntp.go -- nntp interface for peering
//
package srnd

import (
  "bufio"
  "io"
  "log"
  "net/textproto"
  "os"
  "strconv"
  "strings"
  "sync"
  "time"
)

// nntp connection state
type nntpConnection struct {
  // the name of this feed
  name string
  // the mode we are in now
  mode string
  // the policy for federation
  policy FeedPolicy
  // lock help when expecting non pipelined activity
  access sync.Mutex

  // CHECK <message-id>
  check chan string
  // ARTICLE <message-id>
  article chan string
  // TAKETHIS <message-id>
  take chan string
}

func createNNTPConnection() nntpConnection {
  return nntpConnection{
    check: make(chan string, 32),
    article: make(chan string, 32),
    take: make(chan string, 32),
  }
}

// switch modes
func (self *nntpConnection) modeSwitch(mode string, conn *textproto.Conn) (success bool, err error) {
  self.access.Lock()
  mode = strings.ToUpper(mode)
  conn.PrintfLine("MODE %s", mode)
  log.Println("MODE", mode)
  var code int
  code, _, err = conn.ReadCodeLine(-1)
  if code > 200 && code < 300 {
    // accepted mode change
    if len(self.mode) > 0 {
      log.Printf("mode switch %s -> %s", self.mode, mode)
    } else {
      log.Println("switched to mode", mode)
    }
    self.mode = mode
    success = len(self.mode) > 0
  }
  self.access.Unlock()
  return
}

// send a banner for inbound connections
func (self nntpConnection) inboundHandshake(conn *textproto.Conn) (err error) {
  err = conn.PrintfLine("200 Posting Allowed")
  return err
}

// outbound setup, check capabilities and set mode
// returns (supports stream, supports reader) + error
func (self nntpConnection) outboundHandshake(conn *textproto.Conn) (stream, reader bool, err error) {
  log.Println(self.name, "outbound handshake")
  var code int
  var line string
  for err == nil {
    code, line, err = conn.ReadCodeLine(-1)
    if err == nil {
      if code == 200 {
        // send capabilities
        log.Println(self.name, "ask for capabilities")
        err = conn.PrintfLine("CAPABILITIES")
        if err == nil {
          // read response
          dr := conn.DotReader()
          r := bufio.NewReader(dr)
          for {
            line, err = r.ReadString('\n')
            if err == io.EOF {
              // we are at the end of the dotreader
              // set err back to nil and break out
              err = nil
              break
            } else if err == nil {
              // we got a line
              if line == "MODE-READER\n" {
                log.Println(self.name, "supports READER")
                reader = true
              } else if line == "STREAMING\n" {
                stream = true
                log.Println(self.name, "supports STREAMING")
              } else if line == "POSTIHAVESTREAMING\n" {
                stream = true
                reader = false
                log.Println(self.name, "is SRNd")
              }
            } else {
              // we got an error
              log.Println("error reading capabilities", err)
              break
            }
          }
          // return after reading
          return
        }
      } else if code == 201 {
        log.Println("feed", self.name,"does not allow posting")
        // we don't do auth yet
        break
      } else {
        continue
      }
    }
  }
  return
}

// handle streaming event
// this function should send only
func (self nntpConnection) handleStreaming(daemon NNTPDaemon, reader bool, conn *textproto.Conn) (err error) {
  var success bool
  var code int
  var line string
  select {
    // we've are going to ask for an article in READER mode
  case msgid := <- self.article:
    if reader && ValidMessageID(msgid) {
      // switch to reader mode
      self.access.Lock()
      success, err = self.modeSwitch("READER", conn)
      if success {
        // send the request
        conn.PrintfLine("ARTICLE %s", msgid)
        code, line, err = conn.ReadCodeLine(-1)
        if code == 220 {
          // awwww yeh we got it
          f := daemon.store.CreateTempFile(msgid)
          if f == nil {
            // already being loaded elsewhere
          } else {
            // read in the article
            dr := conn.DotReader()
            _, err = io.Copy(f, dr)
            f.Close()
            log.Println(msgid, "got from", self.name)
            // tell daemon to load infeed
            daemon.infeed_load <- msgid
          }
        } else if code == 430 {
          // they don't know it D:
          log.Println(msgid, "not known by", self.name)
        } else {
          // invalid response
          log.Println(self.name, "invald response to ARTICLE:", code, line)
        }
        // now switch back to streaming mode
        log.Println(self.name, "switch back to streaming mode")
        success, err = self.modeSwitch("STREAM", conn)
        if ! success {
          log.Println(self.name, "failed to switch back to streaming mode? wtf!")
        }
      } else {
        log.Println(self.name, "failed to set reader mode")
      }
      self.access.Unlock()
    }
  case msgid := <- self.check:
    log.Println(self.name, "CHECK", msgid)
    conn.PrintfLine("CHECK %s", msgid)
  case msgid := <- self.take:
    // send a file via TAKETHIS
    if ValidMessageID(msgid) {
      fname := daemon.store.GetFilename(msgid)
      if CheckFile(fname) {
        f, err := os.Open(fname)
        if err == nil {
          // time to send
          err = conn.PrintfLine("TAKETHIS %s", msgid)
          dw := conn.DotWriter()
          _ , err = io.Copy(dw, f)
          err = dw.Close()
          f.Close()
        }
      } else {
        log.Println(self.name, "didn't send", msgid, "we don't have it locally")
      }
    }
  }
  return
}

func (self nntpConnection) handleLine(daemon NNTPDaemon, code int, line string, conn *textproto.Conn) (err error) {
  parts := strings.Split(line, " ")
  msgid := parts[0]
  if code == 238 {
    if ValidMessageID(msgid) {
      log.Println("sending", msgid, "to", self.name)
      // send the article to us
      self.take <- msgid
    }
  } else if code == 239 {
    // successful TAKETHIS
    log.Println(msgid, "sent via", self.name)
    // TODO: remember success
  } else if code == 431 {
    // CHECK said we would like this article later
    log.Println("defer sending", msgid, "to", self.name)
    go self.articleDefer(msgid)
  } else if code == 439 {
    // TAKETHIS failed
    log.Println(msgid, "was not sent to", self.name, "denied")
    // TODO: remember denial
  } else if code == 438 {
    // they don't want the article
    // TODO: remeber rejection
  } else {
    // handle command
    parts := strings.Split(line, " ")
    if len(parts) == 2 {
      cmd := parts[0]
      if cmd == "MODE" {
        if parts[1] == "READER" {
          self.mode = "READER"
          log.Println(self.name, "switched to reader mode")
          conn.PrintfLine("201 No posting Permitted")
        } else if parts[1] == "STREAM" {
          // wut? we're already in streaming mode
          log.Println(self.name, "already in streaming mode")
          conn.PrintfLine("203 Streaming enabled brah")
        } else {
          log.Println(self.name, "got invalid mode request", parts[1])
          conn.PrintfLine("501 invalid mode variant:", parts[1])
        }
      } else if cmd == "CHECK" {
        // handle check command
        msgid := parts[1]
        // have we seen this article?
        if daemon.database.HasArticle(msgid) {
          // yeh don't want it
          conn.PrintfLine("438 %s", msgid)
        } else {
          // yes we do want it and we don't have it
          conn.PrintfLine("238 %s", msgid)
        }
      } else if cmd == "TAKETHIS" {
        // read the article headers
        var hdr textproto.MIMEHeader
        hdr, err = conn.ReadMIMEHeader()
        // check the headers and see if we want this article
        newsgroup := hdr.Get("Newsgroups")
        reference := hdr.Get("References")
        msgid := hdr.Get("Message-Id")
        encaddr := hdr.Get("X-Encrypted-Ip")
        torposter := hdr.Get("X-Tor-Poster")
        i2paddr := hdr.Get("X-I2p-Desthash")
        content_type := hdr.Get("Content-Type")
        has_attachment := strings.HasPrefix(content_type, "multipart/mixed")
        pubkey := hdr.Get("X-Pubkey-Ed25519")
        // TODO: allow certain pubkeys?
        is_signed := pubkey != ""
        is_ctl := newsgroup == "ctl" && is_signed
        anon_poster := torposter != "" || i2paddr != "" || encaddr == ""

        if ! newsgroupValidFormat(newsgroup) {
          // invalid newsgroup format
          code = 439
        } else if ! ( ValidMessageID(msgid) && ( reference != "" && ValidMessageID(reference) ) ) {
          // invalid message id or reference
          code = 439
        } else if daemon.store.HasArticle(msgid) {
          // we already have this article locally
          code = 439
        } else if daemon.database.HasArticle(msgid) {
          // we have already seen this article
          log.Println(self.name, "already seen", msgid)
          code = 439
        } else if reference != "" && daemon.database.IsExpired(reference) {
          // this belongs to a root post that is expired or banned
          code = 439
        } else if is_ctl {
          // we always allow control messages
          code = 239
        } else if anon_poster {
          // this was posted anonymously
          if daemon.allow_anon {
            if has_attachment || is_signed {
              // this is a signed message or has attachment
              if daemon.allow_anon_attachments {
                // we'll allow anon attachments
                code = 239
              } else {
                // we don't take signed messages or attachments posted anonymously
                code = 439
              }
            } else {
              // we allow anon posts that are plain
              code = 239
            }
          } else {
            // we don't allow anon posts of any kind
            code = 439
          }
        } else {
          // check for banned address
          var banned bool
          banned, err = daemon.database.CheckEncIPBanned(encaddr)
          if err == nil {
            if banned {
              // this address is banned
              code = 439
            } else {
              // not banned
              code = 239
            }
          } else {
            // an error occured
            log.Println(self.name, "failed to check ban for", encaddr, err)
            // do a 400
            conn.PrintfLine("400 Service temporarily unavailable")
            conn.Close()
            return
          }
        }
        conn.PrintfLine("%d %s", code, msgid)
      }
    }
  }
  return
}

func (self nntpConnection) startStreaming(daemon NNTPDaemon, reader bool, conn *textproto.Conn) {
  var err error
  for err == nil {
    err = self.handleStreaming(daemon, reader, conn)
  }
  log.Println(self.name, "error while streaming:", err)
}

// run the mainloop for this connection
// stream if true means they support streaming mode
// reader if true means they support reader mode
func (self nntpConnection) runConnection(daemon NNTPDaemon, inbound, stream, reader bool, conn *textproto.Conn) {

  var err error
  var line string
  var success bool

  for err == nil {
    if self.mode == "" {
      if inbound  {
        self.name = "inbound-feed"
        // no mode set and inbound
        line, err = conn.ReadLine()
        log.Println(self.name, line)
        parts := strings.Split(line, " ")
        cmd := parts[0]
        if cmd == "CAPABILITIES" {
          // write capabilities
          conn.PrintfLine("101 Capabilities list:")
          dw := conn.DotWriter()
          caps := []string{"VERSION 2", "MODE-READER", "STREAMING"}
          for _, cap := range caps {
            io.WriteString(dw, cap)
            io.WriteString(dw, "\n")
          }
          dw.Close()
          log.Println(self.name, "sent Capabilities")
        } else if cmd == "MODE" {
          if len(parts) == 2 {
            if parts[1] == "READER" {
              // set reader mode
              self.mode = "READER"
              // posting is not permitted with reader mode
              conn.PrintfLine("201 Posting not permitted")
            } else if parts[1] == "STREAM" {
              // set streaming mode
              conn.PrintfLine("203 Stream it brah")
              self.mode = "STREAM"
              log.Println(self.name, "streaming enabled")
              go self.startStreaming(daemon, reader, conn)
            }
          }
        } else {
          log.Println(self.name,"in mode", self.mode, "got invalid inbound line:", line)
          conn.PrintfLine("500 Syntax Error")
        }
      } else {
        // set out mode we are outbound
        if stream {
          success, err = self.modeSwitch("STREAM", conn)
          self.mode = "STREAM"
          if success {
            go self.startStreaming(daemon, reader, conn)
          }
        } else if reader {
          success, err = self.modeSwitch("READER", conn)
          self.mode = "READER"
        }
        if success {
          log.Println(self.name, "mode set to", self.mode)
        } else {
          // bullshit
          // we can't do anything so we quit
          log.Println(self.name, "can't stream or read, wtf?")
          conn.PrintfLine("QUIT")
          conn.Close()
          return
        }
      }
    } else if self.mode == "STREAM" {
      // we're in streaming mode
      line, err = conn.ReadLine()
      if err == nil {
        parts := strings.Split(line, " ")
        var code64 int64
        code64, err = strconv.ParseInt(parts[0], 10, 32)
        if err ==  nil {
          err = self.handleLine(daemon, int(code64), line[4:], conn)
        } else {
          err = self.handleLine(daemon, 0, line, conn)
        }
      }
    } else if self.mode == "READER" {
    }
  }
  log.Println("run connection got error", err)
  if ! inbound {
    conn.PrintfLine("QUIT")
  }
  conn.Close()
}

func (self nntpConnection) articleDefer(msgid string) {
  time.Sleep(time.Second * 90)
  self.check <- msgid
}
<commit_msg>actually load from infeed during takethis<commit_after>//
// nntp.go -- nntp interface for peering
//
package srnd

import (
  "bufio"
  "io"
  "log"
  "net/textproto"
  "os"
  "strconv"
  "strings"
  "sync"
  "time"
)

// nntp connection state
type nntpConnection struct {
  // the name of this feed
  name string
  // the mode we are in now
  mode string
  // the policy for federation
  policy FeedPolicy
  // lock help when expecting non pipelined activity
  access sync.Mutex

  // CHECK <message-id>
  check chan string
  // ARTICLE <message-id>
  article chan string
  // TAKETHIS <message-id>
  take chan string
}

func createNNTPConnection() nntpConnection {
  return nntpConnection{
    check: make(chan string, 32),
    article: make(chan string, 32),
    take: make(chan string, 32),
  }
}

// switch modes
func (self *nntpConnection) modeSwitch(mode string, conn *textproto.Conn) (success bool, err error) {
  self.access.Lock()
  mode = strings.ToUpper(mode)
  conn.PrintfLine("MODE %s", mode)
  log.Println("MODE", mode)
  var code int
  code, _, err = conn.ReadCodeLine(-1)
  if code > 200 && code < 300 {
    // accepted mode change
    if len(self.mode) > 0 {
      log.Printf("mode switch %s -> %s", self.mode, mode)
    } else {
      log.Println("switched to mode", mode)
    }
    self.mode = mode
    success = len(self.mode) > 0
  }
  self.access.Unlock()
  return
}

// send a banner for inbound connections
func (self nntpConnection) inboundHandshake(conn *textproto.Conn) (err error) {
  err = conn.PrintfLine("200 Posting Allowed")
  return err
}

// outbound setup, check capabilities and set mode
// returns (supports stream, supports reader) + error
func (self nntpConnection) outboundHandshake(conn *textproto.Conn) (stream, reader bool, err error) {
  log.Println(self.name, "outbound handshake")
  var code int
  var line string
  for err == nil {
    code, line, err = conn.ReadCodeLine(-1)
    if err == nil {
      if code == 200 {
        // send capabilities
        log.Println(self.name, "ask for capabilities")
        err = conn.PrintfLine("CAPABILITIES")
        if err == nil {
          // read response
          dr := conn.DotReader()
          r := bufio.NewReader(dr)
          for {
            line, err = r.ReadString('\n')
            if err == io.EOF {
              // we are at the end of the dotreader
              // set err back to nil and break out
              err = nil
              break
            } else if err == nil {
              // we got a line
              if line == "MODE-READER\n" {
                log.Println(self.name, "supports READER")
                reader = true
              } else if line == "STREAMING\n" {
                stream = true
                log.Println(self.name, "supports STREAMING")
              } else if line == "POSTIHAVESTREAMING\n" {
                stream = true
                reader = false
                log.Println(self.name, "is SRNd")
              }
            } else {
              // we got an error
              log.Println("error reading capabilities", err)
              break
            }
          }
          // return after reading
          return
        }
      } else if code == 201 {
        log.Println("feed", self.name,"does not allow posting")
        // we don't do auth yet
        break
      } else {
        continue
      }
    }
  }
  return
}

// handle streaming event
// this function should send only
func (self nntpConnection) handleStreaming(daemon NNTPDaemon, reader bool, conn *textproto.Conn) (err error) {
  var success bool
  var code int
  var line string
  select {
    // we've are going to ask for an article in READER mode
  case msgid := <- self.article:
    if reader && ValidMessageID(msgid) {
      // switch to reader mode
      self.access.Lock()
      success, err = self.modeSwitch("READER", conn)
      if success {
        // send the request
        conn.PrintfLine("ARTICLE %s", msgid)
        code, line, err = conn.ReadCodeLine(-1)
        if code == 220 {
          // awwww yeh we got it
          f := daemon.store.CreateTempFile(msgid)
          if f == nil {
            // already being loaded elsewhere
          } else {
            // read in the article
            dr := conn.DotReader()
            _, err = io.Copy(f, dr)
            f.Close()
            log.Println(msgid, "got from", self.name)
            // tell daemon to load infeed
            daemon.infeed_load <- msgid
          }
        } else if code == 430 {
          // they don't know it D:
          log.Println(msgid, "not known by", self.name)
        } else {
          // invalid response
          log.Println(self.name, "invald response to ARTICLE:", code, line)
        }
        // now switch back to streaming mode
        log.Println(self.name, "switch back to streaming mode")
        success, err = self.modeSwitch("STREAM", conn)
        if ! success {
          log.Println(self.name, "failed to switch back to streaming mode? wtf!")
        }
      } else {
        log.Println(self.name, "failed to set reader mode")
      }
      self.access.Unlock()
    }
  case msgid := <- self.check:
    log.Println(self.name, "CHECK", msgid)
    conn.PrintfLine("CHECK %s", msgid)
  case msgid := <- self.take:
    // send a file via TAKETHIS
    if ValidMessageID(msgid) {
      fname := daemon.store.GetFilename(msgid)
      if CheckFile(fname) {
        f, err := os.Open(fname)
        if err == nil {
          // time to send
          err = conn.PrintfLine("TAKETHIS %s", msgid)
          dw := conn.DotWriter()
          _ , err = io.Copy(dw, f)
          err = dw.Close()
          f.Close()
        }
      } else {
        log.Println(self.name, "didn't send", msgid, "we don't have it locally")
      }
    }
  }
  return
}

func (self nntpConnection) handleLine(daemon NNTPDaemon, code int, line string, conn *textproto.Conn) (err error) {
  parts := strings.Split(line, " ")
  msgid := parts[0]
  if code == 238 {
    if ValidMessageID(msgid) {
      log.Println("sending", msgid, "to", self.name)
      // send the article to us
      self.take <- msgid
    }
  } else if code == 239 {
    // successful TAKETHIS
    log.Println(msgid, "sent via", self.name)
    // TODO: remember success
  } else if code == 431 {
    // CHECK said we would like this article later
    log.Println("defer sending", msgid, "to", self.name)
    go self.articleDefer(msgid)
  } else if code == 439 {
    // TAKETHIS failed
    log.Println(msgid, "was not sent to", self.name, "denied")
    // TODO: remember denial
  } else if code == 438 {
    // they don't want the article
    // TODO: remeber rejection
  } else {
    // handle command
    parts := strings.Split(line, " ")
    if len(parts) == 2 {
      cmd := parts[0]
      if cmd == "MODE" {
        if parts[1] == "READER" {
          self.mode = "READER"
          log.Println(self.name, "switched to reader mode")
          conn.PrintfLine("201 No posting Permitted")
        } else if parts[1] == "STREAM" {
          // wut? we're already in streaming mode
          log.Println(self.name, "already in streaming mode")
          conn.PrintfLine("203 Streaming enabled brah")
        } else {
          log.Println(self.name, "got invalid mode request", parts[1])
          conn.PrintfLine("501 invalid mode variant:", parts[1])
        }
      } else if cmd == "CHECK" {
        // handle check command
        msgid := parts[1]
        // have we seen this article?
        if daemon.database.HasArticle(msgid) {
          // yeh don't want it
          conn.PrintfLine("438 %s", msgid)
        } else {
          // yes we do want it and we don't have it
          conn.PrintfLine("238 %s", msgid)
        }
      } else if cmd == "TAKETHIS" {
        // read the article headers
        var hdr textproto.MIMEHeader
        hdr, err = conn.ReadMIMEHeader()
        // check the headers and see if we want this article
        newsgroup := hdr.Get("Newsgroups")
        reference := hdr.Get("References")
        msgid := hdr.Get("Message-Id")
        encaddr := hdr.Get("X-Encrypted-Ip")
        torposter := hdr.Get("X-Tor-Poster")
        i2paddr := hdr.Get("X-I2p-Desthash")
        content_type := hdr.Get("Content-Type")
        has_attachment := strings.HasPrefix(content_type, "multipart/mixed")
        pubkey := hdr.Get("X-Pubkey-Ed25519")
        // TODO: allow certain pubkeys?
        is_signed := pubkey != ""
        is_ctl := newsgroup == "ctl" && is_signed
        anon_poster := torposter != "" || i2paddr != "" || encaddr == ""

        if ! newsgroupValidFormat(newsgroup) {
          // invalid newsgroup format
          code = 439
        } else if ! ( ValidMessageID(msgid) && ( reference != "" && ValidMessageID(reference) ) ) {
          // invalid message id or reference
          code = 439
        } else if daemon.store.HasArticle(msgid) {
          // we already have this article locally
          code = 439
        } else if daemon.database.HasArticle(msgid) {
          // we have already seen this article
          log.Println(self.name, "already seen", msgid)
          code = 439
        } else if reference != "" && daemon.database.IsExpired(reference) {
          // this belongs to a root post that is expired or banned
          code = 439
        } else if is_ctl {
          // we always allow control messages
          code = 239
        } else if anon_poster {
          // this was posted anonymously
          if daemon.allow_anon {
            if has_attachment || is_signed {
              // this is a signed message or has attachment
              if daemon.allow_anon_attachments {
                // we'll allow anon attachments
                code = 239
              } else {
                // we don't take signed messages or attachments posted anonymously
                code = 439
              }
            } else {
              // we allow anon posts that are plain
              code = 239
            }
          } else {
            // we don't allow anon posts of any kind
            code = 439
          }
        } else {
          // check for banned address
          var banned bool
          banned, err = daemon.database.CheckEncIPBanned(encaddr)
          if err == nil {
            if banned {
              // this address is banned
              code = 439
            } else {
              // not banned
              code = 239
            }
          } else {
            // an error occured
            log.Println(self.name, "failed to check ban for", encaddr, err)
            // do a 400
            conn.PrintfLine("400 Service temporarily unavailable")
            conn.Close()
            return
          }
        }
        conn.PrintfLine("%d %s", code, msgid)
        if code == 239 {
          daemon.infeed_load <- msgid
        }
      }
    }
  }
  return
}

func (self nntpConnection) startStreaming(daemon NNTPDaemon, reader bool, conn *textproto.Conn) {
  var err error
  for err == nil {
    err = self.handleStreaming(daemon, reader, conn)
  }
  log.Println(self.name, "error while streaming:", err)
}

// run the mainloop for this connection
// stream if true means they support streaming mode
// reader if true means they support reader mode
func (self nntpConnection) runConnection(daemon NNTPDaemon, inbound, stream, reader bool, conn *textproto.Conn) {

  var err error
  var line string
  var success bool

  for err == nil {
    if self.mode == "" {
      if inbound  {
        self.name = "inbound-feed"
        // no mode set and inbound
        line, err = conn.ReadLine()
        log.Println(self.name, line)
        parts := strings.Split(line, " ")
        cmd := parts[0]
        if cmd == "CAPABILITIES" {
          // write capabilities
          conn.PrintfLine("101 Capabilities list:")
          dw := conn.DotWriter()
          caps := []string{"VERSION 2", "MODE-READER", "STREAMING"}
          for _, cap := range caps {
            io.WriteString(dw, cap)
            io.WriteString(dw, "\n")
          }
          dw.Close()
          log.Println(self.name, "sent Capabilities")
        } else if cmd == "MODE" {
          if len(parts) == 2 {
            if parts[1] == "READER" {
              // set reader mode
              self.mode = "READER"
              // posting is not permitted with reader mode
              conn.PrintfLine("201 Posting not permitted")
            } else if parts[1] == "STREAM" {
              // set streaming mode
              conn.PrintfLine("203 Stream it brah")
              self.mode = "STREAM"
              log.Println(self.name, "streaming enabled")
              go self.startStreaming(daemon, reader, conn)
            }
          }
        } else {
          log.Println(self.name,"in mode", self.mode, "got invalid inbound line:", line)
          conn.PrintfLine("500 Syntax Error")
        }
      } else {
        // set out mode we are outbound
        if stream {
          success, err = self.modeSwitch("STREAM", conn)
          self.mode = "STREAM"
          if success {
            go self.startStreaming(daemon, reader, conn)
          }
        } else if reader {
          success, err = self.modeSwitch("READER", conn)
          self.mode = "READER"
        }
        if success {
          log.Println(self.name, "mode set to", self.mode)
        } else {
          // bullshit
          // we can't do anything so we quit
          log.Println(self.name, "can't stream or read, wtf?")
          conn.PrintfLine("QUIT")
          conn.Close()
          return
        }
      }
    } else if self.mode == "STREAM" {
      // we're in streaming mode
      line, err = conn.ReadLine()
      if err == nil {
        parts := strings.Split(line, " ")
        var code64 int64
        code64, err = strconv.ParseInt(parts[0], 10, 32)
        if err ==  nil {
          err = self.handleLine(daemon, int(code64), line[4:], conn)
        } else {
          err = self.handleLine(daemon, 0, line, conn)
        }
      }
    } else if self.mode == "READER" {
    }
  }
  log.Println("run connection got error", err)
  if ! inbound {
    conn.PrintfLine("QUIT")
  }
  conn.Close()
}

func (self nntpConnection) articleDefer(msgid string) {
  time.Sleep(time.Second * 90)
  self.check <- msgid
}
<|endoftext|>


<commit_before>package sssh

import (
    "bufio"
    . "code.google.com/p/go.crypto/ssh"
    "fmt"
    "io"
    "io/ioutil"
    "job"
    "log"
    "os"
    "os/signal"
)

type Sssh struct {
    User     string
    Password string
    Keyfile  string
    Output   io.Writer
    Cmd      string
    Host     string
    *job.Member
}

func NewS3h(host, user, password, keyfile, cmd string, output io.Writer, mgr *job.Member) (s3h *Sssh) {
    m, _ := mgr.NewMember(host)
    s3h = &Sssh{
        user,
        password,
        keyfile,
        output,
        cmd,
        host,
        m,
    }
    return
}
func getkey(file string) (key Signer, err error) {
    buf, err := ioutil.ReadFile(file)
    if err != nil {
        return
    }
    key, err = ParsePrivateKey(buf)
    if err != nil {
        return
    }
    return

}
func (s3h *Sssh) Work() {
    if s3h.Member != nil {
        s3h.Send("MASTER", map[string]interface{}{"FROM": s3h.Host, "BODY": "BEGIN", "TAG": s3h})
        // Wait for master's reply
        data, _ := s3h.Receive(-1)
        info, _ := data.(map[string]interface{})
        if info["FROM"].(string) != "MASTER" && info["BODY"].(string) != "CONTINUE" {
            return
        }
    }
    auths := []AuthMethod{
        Password(s3h.Password),
    }
    if s3h.Keyfile != "" {
        if key, err := getkey(s3h.Keyfile); err == nil {
            auths = append(auths, PublicKeys(key))
        }
    }
    config := &ClientConfig{
        User: s3h.User,
        Auth: auths,
    }
    conn, err := Dial("tcp", s3h.Host+":22", config)
    if err != nil {
        log.Fatalf("unable to connect: %s", err.Error())
    }
    defer conn.Close()
    session, err := conn.NewSession()
    if err != nil {
        panic("Failed to create session: " + err.Error())
    }
    defer session.Close()

    session.Stdout = s3h.Output
    session.Stderr = s3h.Output
    session.Run(s3h.Cmd)

    if s3h.Member != nil {
        s3h.Send("MASTER", map[string]interface{}{"FROM": s3h.Host, "BODY": "END"})
    }
}

func (s3h *Sssh) Login() {
    auths := []AuthMethod{
        Password(s3h.Password),
    }
    if s3h.Keyfile != "" {
        if key, err := getkey(s3h.Keyfile); err == nil {
            auths = append(auths, PublicKeys(key))
        }
    }
    config := &ClientConfig{
        User: s3h.User,
        Auth: auths,
    }
    conn, err := Dial("tcp", s3h.Host+":22", config)
    if err != nil {
        log.Fatalf("unable to connect: %s", err.Error())
    }
    defer conn.Close()
    session, err := conn.NewSession()
    if err != nil {
        panic("Failed to create session: " + err.Error())
    }
    defer session.Close()
    // Set IO
    session.Stdout = os.Stdout
    session.Stderr = os.Stderr
    in, _ := session.StdinPipe()

    // Set up terminal modes
    modes := TerminalModes{
        ECHO:          0,     // disable echoing
        TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    // Request pseudo terminal
    if err := session.RequestPty("xterm", 80, 200, modes); err != nil {
        log.Fatalf("request for pseudo terminal failed: %s", err)
    }

    // Start remote shell
    if err := session.Shell(); err != nil {
        log.Fatalf("failed to start shell: %s", err)
    }

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    qc := make(chan string)
    go func() {
    Loop:
        for {
            select {
            case <-c:
                session.Signal(SIGINT)
                fmt.Println("")
            case <-qc:
                signal.Stop(c)
                break Loop
            }
        }
    }()
    // Accepting commands
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        fmt.Fprint(in, scanner.Text()+"\n")
    }
    qc <- "Quit signal monitor"
}
<commit_msg>fix bug, fail connect would exit<commit_after>package sssh

import (
    "bufio"
    . "code.google.com/p/go.crypto/ssh"
    "fmt"
    "io"
    "io/ioutil"
    "job"
    "os"
    "os/signal"
)

type Sssh struct {
    User     string
    Password string
    Keyfile  string
    Output   io.Writer
    Cmd      string
    Host     string
    *job.Member
}

func NewS3h(host, user, password, keyfile, cmd string, output io.Writer, mgr *job.Member) (s3h *Sssh) {
    m, _ := mgr.NewMember(host)
    s3h = &Sssh{
        user,
        password,
        keyfile,
        output,
        cmd,
        host,
        m,
    }
    return
}
func getkey(file string) (key Signer, err error) {
    buf, err := ioutil.ReadFile(file)
    if err != nil {
        return
    }
    key, err = ParsePrivateKey(buf)
    if err != nil {
        return
    }
    return

}
func (s3h *Sssh) Work() {
    if s3h.Member != nil {
        s3h.Send("MASTER", map[string]interface{}{"FROM": s3h.Host, "BODY": "BEGIN", "TAG": s3h})
        defer func() {
            s3h.Send("MASTER", map[string]interface{}{"FROM": s3h.Host, "BODY": "END"})
        }()
        // Wait for master's reply
        data, _ := s3h.Receive(-1)
        info, _ := data.(map[string]interface{})
        if info["FROM"].(string) != "MASTER" && info["BODY"].(string) != "CONTINUE" {
            return
        }
    }
    auths := []AuthMethod{
        Password(s3h.Password),
    }
    if s3h.Keyfile != "" {
        if key, err := getkey(s3h.Keyfile); err == nil {
            auths = append(auths, PublicKeys(key))
        }
    }
    config := &ClientConfig{
        User: s3h.User,
        Auth: auths,
    }
    conn, err := Dial("tcp", s3h.Host+":22", config)
    if err != nil {
        fmt.Println("unable to connect: ", err.Error())
        return
    }
    defer conn.Close()
    session, err := conn.NewSession()
    if err != nil {
        fmt.Println("Failed to create session: " + err.Error())
        return
    }
    defer session.Close()

    session.Stdout = s3h.Output
    session.Stderr = s3h.Output
    session.Run(s3h.Cmd)
}

func (s3h *Sssh) Login() {
    auths := []AuthMethod{
        Password(s3h.Password),
    }
    if s3h.Keyfile != "" {
        if key, err := getkey(s3h.Keyfile); err == nil {
            auths = append(auths, PublicKeys(key))
        }
    }
    config := &ClientConfig{
        User: s3h.User,
        Auth: auths,
    }
    conn, err := Dial("tcp", s3h.Host+":22", config)
    if err != nil {
        fmt.Println("unable to connect: ", err.Error())
        return
    }
    defer conn.Close()
    session, err := conn.NewSession()
    if err != nil {
        fmt.Println("Failed to create session: " + err.Error())
        return
    }
    defer session.Close()
    // Set IO
    session.Stdout = os.Stdout
    session.Stderr = os.Stderr
    in, _ := session.StdinPipe()

    // Set up terminal modes
    modes := TerminalModes{
        ECHO:          0,     // disable echoing
        TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    // Request pseudo terminal
    if err := session.RequestPty("xterm", 80, 200, modes); err != nil {
        fmt.Println("request for pseudo terminal failed: %s", err)
        return
    }

    // Start remote shell
    if err := session.Shell(); err != nil {
        fmt.Println("failed to start shell: %s", err)
        return
    }

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    qc := make(chan string)
    go func() {
    Loop:
        for {
            select {
            case <-c:
                session.Signal(SIGINT)
                fmt.Println("")
            case <-qc:
                signal.Stop(c)
                break Loop
            }
        }
    }()
    // Accepting commands
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        fmt.Fprint(in, scanner.Text()+"\n")
    }
    qc <- "Quit signal monitor"
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"

	"github.com/cloudflare/cfssl/cli/genkey"
	"github.com/cloudflare/cfssl/config"
	"github.com/cloudflare/cfssl/csr"
	"github.com/cloudflare/cfssl/initca"
	"github.com/cloudflare/cfssl/signer"
	"github.com/cloudflare/cfssl/signer/universal"
	"github.com/gholt/ring"
)

func main() {
	args := os.Args[1:]
	if len(args) == 0 {
		args = []string{"help"}
	}
	for i := 0; i < len(args); i++ {
		switch args[i] {
		case "help":
			fmt.Println("init       Creates a new CA and initial ring builder file")
			fmt.Println("add <ip>   Adds the IP to the ring and creates a new certificate for it")
			os.Exit(1)
		case "init":
			for _, name := range []string{"/etc/cfsd/ca.pem", "/etc/cfsd/ca-key.pem", "/etc/cfsd/cfs.builder", "/etc/cfsd/cfs.ring"} {
				if _, err := os.Lstat(name); err == nil {
					panic(name + " already exists")
				}
			}
			cert, _, key, err := initca.New(&csr.CertificateRequest{
				CN:         "CA",
				KeyRequest: &csr.BasicKeyRequest{A: "rsa", S: 2048},
			})
			if err != nil {
				panic(err)
			}
			if err = ioutil.WriteFile("/etc/cfsd/ca.pem", cert, 0664); err != nil {
				panic(err)
			}
			if err = ioutil.WriteFile("/etc/cfsd/ca-key.pem", key, 0600); err != nil {
				panic(err)
			}
			if err = ring.CLI([]string{"cfsadm init", "/etc/cfsd/cfs.builder", "create"}, os.Stdout, false); err != nil {
				panic(err)
			}
		case "add":
			i++
			if i >= len(args) {
				panic("The 'add' command requires the IP address to add.")
			}
			ipAddr := args[i]
			for _, name := range []string{"/etc/cfsd/" + ipAddr + ".pem", "/etc/cfsd/" + ipAddr + "-key.pem"} {
				if _, err := os.Lstat(name); err == nil {
					panic(name + " already exists")
				}
			}
			csrBytes, key, err := (&csr.Generator{
				Validator: genkey.Validator,
			}).ProcessRequest(&csr.CertificateRequest{
				CN:         "CA",
				KeyRequest: &csr.BasicKeyRequest{A: "rsa", S: 2048},
			})
			if err != nil {
				panic(err)
			}
			root := universal.Root{
				Config: map[string]string{
					"cert-file": "/etc/cfsd/ca.pem",
					"key-file":  "/etc/cfsd/ca-key.pem",
				},
			}
			policy := &config.Signing{
				Profiles: map[string]*config.SigningProfile{},
				Default:  config.DefaultConfig(),
			}
			signerr, err := universal.NewSigner(root, policy)
			if err != nil {
				panic(err)
			}
			cert, err := signerr.Sign(signer.SignRequest{
				Request: string(csrBytes),
				Hosts:   []string{ipAddr},
			})
			if err != nil {
				panic(err)
			}
			if err = ioutil.WriteFile("/etc/cfsd/"+ipAddr+".pem", cert, 0664); err != nil {
				panic(err)
			}
			if err = ioutil.WriteFile("/etc/cfsd/"+ipAddr+"-key.pem", key, 0600); err != nil {
				panic(err)
			}
			replicaCount := 3
			if _, b, err := ring.RingOrBuilder("/etc/cfsd/cfs.builder"); err != nil || b == nil {
				panic(err)
			} else if len(b.Nodes())+1 < replicaCount {
				replicaCount = len(b.Nodes()) + 1
			}
			if err = ring.CLI([]string{"cfsadm add", "/etc/cfsd/cfs.builder", "set-replicas", fmt.Sprintf("%d", replicaCount)}, os.Stdout, false); err != nil {
				panic(err)
			}
			if err = ring.CLI([]string{"cfsadm add", "/etc/cfsd/cfs.builder", "add", "address0=" + ipAddr + ":12300", "address1=" + ipAddr + ":12310", "address2=" + ipAddr + ":12311", "address3=" + ipAddr + ":12320", "address4=" + ipAddr + ":12321"}, os.Stdout, false); err != nil {
				panic(err)
			}
			if err = ring.CLI([]string{"cfsadm add", "/etc/cfsd/cfs.builder", "ring"}, os.Stdout, false); err != nil {
				panic(err)
			}
		default:
			panic(args[i])
		}
	}
}
<commit_msg>Mkdir /etc/cfsd<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"

	"github.com/cloudflare/cfssl/cli/genkey"
	"github.com/cloudflare/cfssl/config"
	"github.com/cloudflare/cfssl/csr"
	"github.com/cloudflare/cfssl/initca"
	"github.com/cloudflare/cfssl/signer"
	"github.com/cloudflare/cfssl/signer/universal"
	"github.com/gholt/ring"
)

func main() {
	args := os.Args[1:]
	if len(args) == 0 {
		args = []string{"help"}
	}
	for i := 0; i < len(args); i++ {
		switch args[i] {
		case "help":
			fmt.Println("init       Creates a new CA and initial ring builder file")
			fmt.Println("add <ip>   Adds the IP to the ring and creates a new certificate for it")
			os.Exit(1)
		case "init":
			if err := os.Mkdir("/etc/cfsd", 0700); err != nil && !os.IsExist(err) {
				panic(err)
			}
			for _, name := range []string{"/etc/cfsd/ca.pem", "/etc/cfsd/ca-key.pem", "/etc/cfsd/cfs.builder", "/etc/cfsd/cfs.ring"} {
				if _, err := os.Lstat(name); err == nil {
					panic(name + " already exists")
				}
			}
			cert, _, key, err := initca.New(&csr.CertificateRequest{
				CN:         "CA",
				KeyRequest: &csr.BasicKeyRequest{A: "rsa", S: 2048},
			})
			if err != nil {
				panic(err)
			}
			if err = ioutil.WriteFile("/etc/cfsd/ca.pem", cert, 0664); err != nil {
				panic(err)
			}
			if err = ioutil.WriteFile("/etc/cfsd/ca-key.pem", key, 0600); err != nil {
				panic(err)
			}
			if err = ring.CLI([]string{"cfsadm init", "/etc/cfsd/cfs.builder", "create"}, os.Stdout, false); err != nil {
				panic(err)
			}
		case "add":
			i++
			if i >= len(args) {
				panic("The 'add' command requires the IP address to add.")
			}
			ipAddr := args[i]
			for _, name := range []string{"/etc/cfsd/" + ipAddr + ".pem", "/etc/cfsd/" + ipAddr + "-key.pem"} {
				if _, err := os.Lstat(name); err == nil {
					panic(name + " already exists")
				}
			}
			csrBytes, key, err := (&csr.Generator{
				Validator: genkey.Validator,
			}).ProcessRequest(&csr.CertificateRequest{
				CN:         "CA",
				KeyRequest: &csr.BasicKeyRequest{A: "rsa", S: 2048},
			})
			if err != nil {
				panic(err)
			}
			root := universal.Root{
				Config: map[string]string{
					"cert-file": "/etc/cfsd/ca.pem",
					"key-file":  "/etc/cfsd/ca-key.pem",
				},
			}
			policy := &config.Signing{
				Profiles: map[string]*config.SigningProfile{},
				Default:  config.DefaultConfig(),
			}
			signerr, err := universal.NewSigner(root, policy)
			if err != nil {
				panic(err)
			}
			cert, err := signerr.Sign(signer.SignRequest{
				Request: string(csrBytes),
				Hosts:   []string{ipAddr},
			})
			if err != nil {
				panic(err)
			}
			if err = ioutil.WriteFile("/etc/cfsd/"+ipAddr+".pem", cert, 0664); err != nil {
				panic(err)
			}
			if err = ioutil.WriteFile("/etc/cfsd/"+ipAddr+"-key.pem", key, 0600); err != nil {
				panic(err)
			}
			replicaCount := 3
			if _, b, err := ring.RingOrBuilder("/etc/cfsd/cfs.builder"); err != nil || b == nil {
				panic(err)
			} else if len(b.Nodes())+1 < replicaCount {
				replicaCount = len(b.Nodes()) + 1
			}
			if err = ring.CLI([]string{"cfsadm add", "/etc/cfsd/cfs.builder", "set-replicas", fmt.Sprintf("%d", replicaCount)}, os.Stdout, false); err != nil {
				panic(err)
			}
			if err = ring.CLI([]string{"cfsadm add", "/etc/cfsd/cfs.builder", "add", "address0=" + ipAddr + ":12300", "address1=" + ipAddr + ":12310", "address2=" + ipAddr + ":12311", "address3=" + ipAddr + ":12320", "address4=" + ipAddr + ":12321"}, os.Stdout, false); err != nil {
				panic(err)
			}
			if err = ring.CLI([]string{"cfsadm add", "/etc/cfsd/cfs.builder", "ring"}, os.Stdout, false); err != nil {
				panic(err)
			}
		default:
			panic(args[i])
		}
	}
}
<|endoftext|>


<commit_before>// chkdomain runs whois lookups against one or more domain names and prints
// those that are available to stdout.
package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"io"
	"net"
	"os"
	"regexp"
	"strings"
	"time"
)

var (
	// Regex for detecting whois response indicating domain is available
	availableRE = regexp.MustCompile(`\b(is not registered|is available|no match for|not found)\b`)
	// Regex for validating a domain, to prevent things like '--foo' from being queried
	domainRE = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-\.]{0,61}[a-zA-Z0-9])?\.[a-zA-Z]{2,}$`)
	// Time in ms past epoch when program started
	startMS = time.Now().UnixNano() / int64(time.Millisecond)
)

// The result of a whois check for a single domain.
type Result struct {
	domain    string
	output    string
	available bool
	err       error
}

// A currently running job to lookup info for a single domain.
type Job struct {
	domain  string
	results chan<- Result
}

// Run job and send result to 'results' channel.
func (job Job) Run() {
	result := Result{domain: job.domain}
	if whoisOutput, err := whois(result.domain); err != nil {
		result.err = err
	} else {
		result.output = whoisOutput
		result.available = isDomainAvailable(whoisOutput)
	}
	job.results <- result
}

// Answer whether domain appears to be available based on whois text result.
func isDomainAvailable(whoisText string) bool {
	return availableRE.FindString(strings.ToLower(whoisText)) != ""
}

// Answer whether domain is valid by validating against domainRE regex.
func isDomainValid(domain string) bool {
	return !!domainRE.MatchString(domain)
}

// Get the whois server (including port) for querying a given domain.
func getWhoisServer(domain string) string {
	segments := strings.Split(domain, ".")
	tld := segments[len(segments)-1]
	if len(segments) > 2 {
		tld = segments[len(segments)-2] + "." + tld
	}
	return tld + ".whois-servers.net:43"
}

// Run a whois check for the given domain, returning the non-empty string
// result of the lookup (and nil) on success, or an empty string and error
// on failure.
func whois(domain string) (string, error) {
	if !isDomainValid(domain) {
		return "", errors.New(fmt.Sprintf("invalid domain: %s", domain))
	}
	whoisServer := getWhoisServer(domain)
	if conn, connErr := net.Dial("tcp", whoisServer); connErr != nil {
		msg := fmt.Sprint("error connecting to %v: %v", whoisServer, connErr)
		return "", errors.New(msg)
	} else {
		if _, wrtErr := conn.Write([]byte(domain + "\r\n")); wrtErr != nil {
			return "", wrtErr
		}
		buf := make([]byte, 1024)
		res := []byte{}
		for {
			if numBytes, readErr := conn.Read(buf); numBytes == 0 && readErr != io.EOF {
				return "", readErr
			} else {
				res = append(res, buf[0:numBytes]...)
				if readErr == io.EOF {
					break
				}
			}
		}
		return string(res), nil
	}
}

// Process results as they are ready, closing 'done' after all expected
// results have been processed (based on numDomains).
func processResults(results chan Result, numDomains int, done chan struct{}, debug bool) {
	processed := 0
	for result := range results {
		processed += 1
		if result.err != nil {
			fmt.Printf("%s\n", result.err)
			continue
		}
		if debug {
			fmt.Printf("[%d]\t", (time.Now().UnixNano()/int64(time.Millisecond))-startMS)
		}
		if result.available {
			if debug {
				fmt.Printf("AVAILABLE\t")
			}
			fmt.Println(result.domain)
		} else {
			if debug {
				fmt.Printf("UNAVAILABLE\t")
				fmt.Println(result.domain)
			}
		}
		if processed >= numDomains {
			close(done)
			return
		}
	}
}

func readLines() ([]string, error) {
	bio := bufio.NewReader(os.Stdin)
	lines := make([]string, 0, 8)
	for {
		line, err := bio.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				break
			} else {
				return lines, err
			}
		}
		lines = append(lines, line[:len(line)-1])
	}
	return lines, nil
}

func main() {

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: chkdomain [options] <DOMAIN> [DOMAIN...]\n\n")
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, "\n  DOMAIN domain name to check, or - to read from stdin (one per line)")
		os.Exit(1)
	}

	debug := flag.Bool("debug", false, "print debug info (all results, with times)")
	flag.Parse()

	// At least 1 arg is required, so print usage and fail if none given.
	if len(os.Args) < 2 {
		flag.Usage()
	}

	domains := flag.Args()
	numDomains := len(domains)

	// If there's just one arg, check if it's '-' to indicate that
	// domains will be provided one-per-line via stdin, and read them if so.
	if numDomains == 1 && domains[0] == "-" {
		fileDomains, err := readLines()
		if err != nil {
			fmt.Printf("error reading domains from stdin: %s\n", err)
			os.Exit(1)
		}
		domains = fileDomains
		numDomains = len(domains)
	}

	results := make(chan Result, numDomains)
	done := make(chan struct{})

	// Run all jobs concurrently
	for _, domain := range domains {
		go Job{domain, results}.Run()
	}

	// Process results, waiting on 'done' to be closed after all
	// expected results have been processed.
	go processResults(results, numDomains, done, *debug)
	<-done
}
<commit_msg>improved result handling, more permissive arg parsing, and cleanup<commit_after>// chkdomain runs whois lookups against one or more domain names and prints
// those that are available to stdout.
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"net"
	"os"
	"regexp"
	"strings"
	"time"
)

var (
	// Regex for detecting whois response indicating domain is available
	availableRE = regexp.MustCompile(`\b(is not registered|is available|no match for|not found)\b`)
	// Regex for validating a domain, to prevent things like '--foo' from being queried
	domainRE = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-\.]{0,61}[a-zA-Z0-9])?\.[a-zA-Z]{2,}$`)
	// Time in ms past epoch when program started
	startMS = time.Now().UnixNano() / int64(time.Millisecond)
)

// The result of a whois check for a single domain.
type Result struct {
	domain    string
	output    string
	available bool
	err       error
}

// Print result of a whois check, optionally including extra debug info.
func (result Result) Print(debug bool) {
	if result.err != nil {
		fmt.Printf("%s\n", result.err)
		return
	}
	if debug {
		fmt.Printf("[%d]\t", (time.Now().UnixNano()/int64(time.Millisecond))-startMS)
	}
	if result.available {
		if debug {
			fmt.Printf("AVAILABLE\t")
		}
		fmt.Println(result.domain)
	} else {
		if debug {
			fmt.Printf("UNAVAILABLE\t")
			fmt.Println(result.domain)
		}
	}
}

// A currently running job to lookup info for a single domain.
type Job struct {
	domain  string
	results chan<- Result
}

// Run job and send result to 'results' channel.
func (job Job) Run() {
	result := Result{domain: job.domain}
	if whoisOutput, err := whois(result.domain); err != nil {
		result.err = err
	} else {
		result.output = whoisOutput
		result.available = isDomainAvailable(whoisOutput)
	}
	job.results <- result
}

// Answer whether domain appears to be available based on whois text result.
func isDomainAvailable(whoisText string) bool {
	return availableRE.FindString(strings.ToLower(whoisText)) != ""
}

// Answer whether domain is valid by validating against domainRE regex.
func isDomainValid(domain string) bool {
	return !!domainRE.MatchString(domain)
}

// Get the whois server (including port) for querying a given domain.
func getWhoisServer(domain string) string {
	segments := strings.Split(domain, ".")
	tld := segments[len(segments)-1]
	if len(segments) > 2 {
		tld = segments[len(segments)-2] + "." + tld
	}
	return tld + ".whois-servers.net:43"
}

// Run a whois check for the given domain, returning a non-empty string result
// of the lookup (and nil) on success, or an empty string and error on failure.
func whois(domain string) (string, error) {
	if !isDomainValid(domain) {
		return "", fmt.Errorf("invalid domain: %s", domain)
	}
	whoisServer := getWhoisServer(domain)

	conn, connErr := net.Dial("tcp4", whoisServer)
	if connErr != nil {
		return "", fmt.Errorf("error connecting to %v: %v", whoisServer, connErr)
	}

	_, wrtErr := conn.Write([]byte(domain + "\r\n"))
	if wrtErr != nil {
		return "", fmt.Errorf("error writing to socket: %v", wrtErr)
	}

	buf := make([]byte, 1024)
	res := []byte{}
	for {
		numBytes, readErr := conn.Read(buf)
		if numBytes == 0 && readErr != io.EOF {
			return "", readErr
		}
		res = append(res, buf[0:numBytes]...)
		if readErr == io.EOF {
			break
		}
	}
	return string(res), nil
}

// Read whitespace-delimited words from stdin and split on space and/or newline,
// returning ([]string, nil) on success or ("", error) on failure.
func readWords() ([]string, error) {
	bio := bufio.NewReader(os.Stdin)
	lines := make([]string, 0, 16)
	for {
		line, err := bio.ReadString('\n')
		if err != nil && err != io.EOF {
			return lines, err
		}
		for _, word := range strings.Split(strings.TrimRight(line, "\n"), " ") {
			word = strings.Trim(word, " ")
			if word != "" {
				lines = append(lines, word)
			}
		}
		if err == io.EOF {
			break
		}
	}
	return lines, nil
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: chkdomain [options] <DOMAIN> [DOMAIN...]\n\n")
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, "\n  DOMAIN domain name to check, or - to read from stdin (one per line)")
		os.Exit(1)
	}

	debug := flag.Bool("debug", false, "print debug info (all results, with times)")
	flag.Parse()

	// At least 1 arg is required, so print usage and fail if none given.
	if len(os.Args) < 2 {
		flag.Usage()
	}

	domains := flag.Args()
	numDomains := len(domains)

	// If there's just one arg, check if it's '-' to indicate that
	// domains will be provided one-per-line via stdin, and read them if so.
	if numDomains == 1 && domains[0] == "-" {
		fileDomains, err := readWords()
		if err != nil {
			fmt.Fprintf(os.Stderr, "error reading domains from stdin: %v", err)
			os.Exit(1)
		}
		domains = fileDomains
		numDomains = len(domains)
	}

	// Results channel to which each job doing the lookup in a goroutine
	// will send its result upon completion
	results := make(chan Result, numDomains)

	// Start all coroutines
	for _, domain := range domains {
		go Job{domain, results}.Run()
	}

	// Handle result of each as it is available
	for _ = range domains {
		result := <-results
		result.Print(*debug)
	}
}
<|endoftext|>


<commit_before>/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package cgroups

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"testing"

	specs "github.com/opencontainers/runtime-spec/specs-go"
)

// using t.Error in test were defers do cleanup on the filesystem

func TestCreate(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if control == nil {
		t.Error("control is nil")
		return
	}
	for _, s := range Subsystems() {
		if _, err := os.Stat(filepath.Join(mock.root, string(s), "test")); err != nil {
			if os.IsNotExist(err) {
				t.Errorf("group %s was not created", s)
				return
			}
			t.Errorf("group %s was not created correctly %s", s, err)
			return
		}
	}
}

func TestStat(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	s, err := control.Stat(IgnoreNotExist)
	if err != nil {
		t.Error(err)
		return
	}
	if s == nil {
		t.Error("stat result is nil")
		return
	}
}

func TestAdd(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.Add(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkPid(mock, filepath.Join(string(s), "test"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
}

func TestAddTask(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.AddTask(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkTaskid(mock, filepath.Join(string(s), "test"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
}

func TestListPids(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.Add(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkPid(mock, filepath.Join(string(s), "test"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
	procs, err := control.Processes(Freezer, false)
	if err != nil {
		t.Error(err)
		return
	}
	if l := len(procs); l != 1 {
		t.Errorf("should have one process but received %d", l)
		return
	}
	if procs[0].Pid != 1234 {
		t.Errorf("expected pid %d but received %d", 1234, procs[0].Pid)
	}
}

func TestListTasksPids(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.AddTask(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkTaskid(mock, filepath.Join(string(s), "test"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
	tasks, err := control.Tasks(Freezer, false)
	if err != nil {
		t.Error(err)
		return
	}
	if l := len(tasks); l != 1 {
		t.Errorf("should have one task but received %d", l)
		return
	}
	if tasks[0].Pid != 1234 {
		t.Errorf("expected task pid %d but received %d", 1234, tasks[0].Pid)
	}
}

func readValue(mock *mockCgroup, path string) (string, error) {
	data, err := ioutil.ReadFile(filepath.Join(mock.root, path))
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func checkPid(mock *mockCgroup, path string, expected int) error {
	data, err := readValue(mock, filepath.Join(path, cgroupProcs))
	if err != nil {
		return err
	}
	v, err := strconv.Atoi(string(data))
	if err != nil {
		return err
	}
	if v != expected {
		return fmt.Errorf("expectd pid %d but received %d", expected, v)
	}
	return nil
}

func checkTaskid(mock *mockCgroup, path string, expected int) error {
	data, err := readValue(mock, filepath.Join(path, cgroupTasks))
	if err != nil {
		return err
	}
	v, err := strconv.Atoi(string(data))
	if err != nil {
		return err
	}
	if v != expected {
		return fmt.Errorf("expectd task id %d but received %d", expected, v)
	}
	return nil
}

func mockNewNotInRdma(subsystems []Subsystem, path Path, resources *specs.LinuxResources) (Cgroup, error) {
	for _, s := range subsystems {
		if s.Name() != Rdma {
			if err := initializeSubsystem(s, path, resources); err != nil {
				return nil, err
			}
		}
	}
	return &cgroup{
		path:       path,
		subsystems: subsystems,
	}, nil
}

func TestLoad(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if control, err = Load(mock.hierarchy, StaticPath("test")); err != nil {
		t.Error(err)
		return
	}
	if control == nil {
		t.Error("control is nil")
		return
	}
}

func TestLoadWithMissingSubsystems(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	subsystems, err := mock.hierarchy()
	if err != nil {
		t.Error(err)
		return
	}
	control, err := mockNewNotInRdma(subsystems, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if control, err = Load(mock.hierarchy, StaticPath("test")); err != nil {
		t.Error(err)
		return
	}
	if control == nil {
		t.Error("control is nil")
		return
	}
	if len(control.Subsystems()) != len(subsystems) - 1 {
		t.Error("wrong number of active subsystems")
		return
	}
}

func TestDelete(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.Delete(); err != nil {
		t.Error(err)
	}
}

func TestCreateSubCgroup(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	sub, err := control.New("child", &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := sub.Add(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkPid(mock, filepath.Join(string(s), "test", "child"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
	if err := sub.AddTask(Process{Pid: 5678}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkTaskid(mock, filepath.Join(string(s), "test", "child"), 5678); err != nil {
			t.Error(err)
			return
		}
	}
}

func TestFreezeThaw(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.Freeze(); err != nil {
		t.Error(err)
		return
	}
	if state := control.State(); state != Frozen {
		t.Errorf("expected %q but received %q", Frozen, state)
		return
	}
	if err := control.Thaw(); err != nil {
		t.Error(err)
		return
	}
	if state := control.State(); state != Thawed {
		t.Errorf("expected %q but received %q", Thawed, state)
		return
	}
}

func TestSubsystems(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	cache := make(map[Name]struct{})
	for _, s := range control.Subsystems() {
		cache[s.Name()] = struct{}{}
	}
	for _, s := range Subsystems() {
		if _, ok := cache[s]; !ok {
			t.Errorf("expected subsystem %q but not found", s)
		}
	}
}

func TestCpusetParent(t *testing.T) {
	const expected = "0-3"
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("/parent/child"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	defer control.Delete()
	for _, file := range []string{
		"parent/cpuset.cpus",
		"parent/cpuset.mems",
		"parent/child/cpuset.cpus",
		"parent/child/cpuset.mems",
	} {
		v, err := readValue(mock, filepath.Join(string(Cpuset), file))
		if err != nil {
			t.Error(err)
			return
		}
		if v != expected {
			t.Errorf("expected %q for %s but received %q", expected, file, v)
		}
	}
}
<commit_msg>Correct ineffassign warning<commit_after>/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package cgroups

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"testing"

	specs "github.com/opencontainers/runtime-spec/specs-go"
)

// using t.Error in test were defers do cleanup on the filesystem

func TestCreate(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if control == nil {
		t.Error("control is nil")
		return
	}
	for _, s := range Subsystems() {
		if _, err := os.Stat(filepath.Join(mock.root, string(s), "test")); err != nil {
			if os.IsNotExist(err) {
				t.Errorf("group %s was not created", s)
				return
			}
			t.Errorf("group %s was not created correctly %s", s, err)
			return
		}
	}
}

func TestStat(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	s, err := control.Stat(IgnoreNotExist)
	if err != nil {
		t.Error(err)
		return
	}
	if s == nil {
		t.Error("stat result is nil")
		return
	}
}

func TestAdd(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.Add(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkPid(mock, filepath.Join(string(s), "test"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
}

func TestAddTask(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.AddTask(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkTaskid(mock, filepath.Join(string(s), "test"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
}

func TestListPids(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.Add(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkPid(mock, filepath.Join(string(s), "test"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
	procs, err := control.Processes(Freezer, false)
	if err != nil {
		t.Error(err)
		return
	}
	if l := len(procs); l != 1 {
		t.Errorf("should have one process but received %d", l)
		return
	}
	if procs[0].Pid != 1234 {
		t.Errorf("expected pid %d but received %d", 1234, procs[0].Pid)
	}
}

func TestListTasksPids(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.AddTask(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkTaskid(mock, filepath.Join(string(s), "test"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
	tasks, err := control.Tasks(Freezer, false)
	if err != nil {
		t.Error(err)
		return
	}
	if l := len(tasks); l != 1 {
		t.Errorf("should have one task but received %d", l)
		return
	}
	if tasks[0].Pid != 1234 {
		t.Errorf("expected task pid %d but received %d", 1234, tasks[0].Pid)
	}
}

func readValue(mock *mockCgroup, path string) (string, error) {
	data, err := ioutil.ReadFile(filepath.Join(mock.root, path))
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func checkPid(mock *mockCgroup, path string, expected int) error {
	data, err := readValue(mock, filepath.Join(path, cgroupProcs))
	if err != nil {
		return err
	}
	v, err := strconv.Atoi(string(data))
	if err != nil {
		return err
	}
	if v != expected {
		return fmt.Errorf("expectd pid %d but received %d", expected, v)
	}
	return nil
}

func checkTaskid(mock *mockCgroup, path string, expected int) error {
	data, err := readValue(mock, filepath.Join(path, cgroupTasks))
	if err != nil {
		return err
	}
	v, err := strconv.Atoi(string(data))
	if err != nil {
		return err
	}
	if v != expected {
		return fmt.Errorf("expectd task id %d but received %d", expected, v)
	}
	return nil
}

func mockNewNotInRdma(subsystems []Subsystem, path Path, resources *specs.LinuxResources) (Cgroup, error) {
	for _, s := range subsystems {
		if s.Name() != Rdma {
			if err := initializeSubsystem(s, path, resources); err != nil {
				return nil, err
			}
		}
	}
	return &cgroup{
		path:       path,
		subsystems: subsystems,
	}, nil
}

func TestLoad(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if control, err = Load(mock.hierarchy, StaticPath("test")); err != nil {
		t.Error(err)
		return
	}
	if control == nil {
		t.Error("control is nil")
		return
	}
}

func TestLoadWithMissingSubsystems(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	subsystems, err := mock.hierarchy()
	if err != nil {
		t.Error(err)
		return
	}
	control, err := mockNewNotInRdma(subsystems, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if control == nil {
		t.Error("control is nil")
		return
	}
	if control, err = Load(mock.hierarchy, StaticPath("test")); err != nil {
		t.Error(err)
		return
	}
	if control == nil {
		t.Error("control is nil")
		return
	}
	if len(control.Subsystems()) != len(subsystems) - 1 {
		t.Error("wrong number of active subsystems")
		return
	}
}

func TestDelete(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.Delete(); err != nil {
		t.Error(err)
	}
}

func TestCreateSubCgroup(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	sub, err := control.New("child", &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := sub.Add(Process{Pid: 1234}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkPid(mock, filepath.Join(string(s), "test", "child"), 1234); err != nil {
			t.Error(err)
			return
		}
	}
	if err := sub.AddTask(Process{Pid: 5678}); err != nil {
		t.Error(err)
		return
	}
	for _, s := range Subsystems() {
		if err := checkTaskid(mock, filepath.Join(string(s), "test", "child"), 5678); err != nil {
			t.Error(err)
			return
		}
	}
}

func TestFreezeThaw(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	if err := control.Freeze(); err != nil {
		t.Error(err)
		return
	}
	if state := control.State(); state != Frozen {
		t.Errorf("expected %q but received %q", Frozen, state)
		return
	}
	if err := control.Thaw(); err != nil {
		t.Error(err)
		return
	}
	if state := control.State(); state != Thawed {
		t.Errorf("expected %q but received %q", Thawed, state)
		return
	}
}

func TestSubsystems(t *testing.T) {
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("test"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	cache := make(map[Name]struct{})
	for _, s := range control.Subsystems() {
		cache[s.Name()] = struct{}{}
	}
	for _, s := range Subsystems() {
		if _, ok := cache[s]; !ok {
			t.Errorf("expected subsystem %q but not found", s)
		}
	}
}

func TestCpusetParent(t *testing.T) {
	const expected = "0-3"
	mock, err := newMock()
	if err != nil {
		t.Fatal(err)
	}
	defer mock.delete()
	control, err := New(mock.hierarchy, StaticPath("/parent/child"), &specs.LinuxResources{})
	if err != nil {
		t.Error(err)
		return
	}
	defer control.Delete()
	for _, file := range []string{
		"parent/cpuset.cpus",
		"parent/cpuset.mems",
		"parent/child/cpuset.cpus",
		"parent/child/cpuset.mems",
	} {
		v, err := readValue(mock, filepath.Join(string(Cpuset), file))
		if err != nil {
			t.Error(err)
			return
		}
		if v != expected {
			t.Errorf("expected %q for %s but received %q", expected, file, v)
		}
	}
}
<|endoftext|>


<commit_before>package handler

import (
	"net/http"

	"github.com/google/go-github/github"
	"github.com/mattbostock/timeoff/model"
	"golang.org/x/oauth2"
)

func GithubOauthCallback(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")
	state := r.URL.Query().Get("state")

	stateCookie, _ := r.Cookie(oauthStateCookieName)

	if state != stateCookie.Value {
		log.Errorln("GitHub state mismatch during Oauth callback")
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	http.SetCookie(w, &http.Cookie{
		Name:   oauthStateCookieName,
		MaxAge: -1,
	})

	t, err := oauthConfig.Exchange(oauth2.NoContext, code)
	if err != nil {
		log.Errorf("GitHub Oauth exchange failed: %s", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	httpClient := oauthConfig.Client(oauth2.NoContext, t)
	githubClient := github.NewClient(httpClient)

	// set this explicitly as we'll override it in tests
	githubClient.BaseURL = githubAPIBaseURL

	// Get authenticated user based on Oauth token provided in HTTP client config
	// An empty string means get the authenticated user
	user, _, err := githubClient.Users.Get("")

	if err != nil {
		log.Infoln("Authentication failed")
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	u := &model.User{
		Name:     *user.Name,
		GitHubID: uint64(*user.ID),
	}

	emails, _, err := githubClient.Users.ListEmails(nil)
	if err != nil {
		log.Errorln(err)
	}

	for _, e := range emails {
		if *e.Primary && *e.Verified {
			u.Email = *e.Email
			break
		}
	}

	u.UpdateOrCreate()

	err = sessionManager.SetCookie(w, uint64(u.ID))
	if err != nil {
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	http.Redirect(w, r, "/dashboard", http.StatusTemporaryRedirect)
}
<commit_msg>Pass down authentication error from GitHub Oauth<commit_after>package handler

import (
	"net/http"

	"github.com/google/go-github/github"
	"github.com/mattbostock/timeoff/model"
	"golang.org/x/oauth2"
)

func GithubOauthCallback(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")
	state := r.URL.Query().Get("state")

	stateCookie, _ := r.Cookie(oauthStateCookieName)

	if state != stateCookie.Value {
		log.Errorln("GitHub state mismatch during Oauth callback")
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	http.SetCookie(w, &http.Cookie{
		Name:   oauthStateCookieName,
		MaxAge: -1,
	})

	t, err := oauthConfig.Exchange(oauth2.NoContext, code)
	if err != nil {
		log.Errorf("GitHub Oauth exchange failed: %s", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	httpClient := oauthConfig.Client(oauth2.NoContext, t)
	githubClient := github.NewClient(httpClient)

	// set this explicitly as we'll override it in tests
	githubClient.BaseURL = githubAPIBaseURL

	// Get authenticated user based on Oauth token provided in HTTP client config
	// An empty string means get the authenticated user
	user, _, err := githubClient.Users.Get("")
	if err != nil {
		log.Infof("GitHub authentication failed: %s", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	u := &model.User{
		Name:     *user.Name,
		GitHubID: uint64(*user.ID),
	}

	emails, _, err := githubClient.Users.ListEmails(nil)
	if err != nil {
		log.Errorln(err)
	}

	for _, e := range emails {
		if *e.Primary && *e.Verified {
			u.Email = *e.Email
			break
		}
	}

	u.UpdateOrCreate()

	err = sessionManager.SetCookie(w, uint64(u.ID))
	if err != nil {
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	http.Redirect(w, r, "/dashboard", http.StatusTemporaryRedirect)
}
<|endoftext|>


<commit_before>package api

import "github.com/danmane/abalone/go/game"

type Game struct {
	ID int64 `gorm:"column:id"`

	MatchId int64 `gorm:"column:match_id"`
	WhiteId int64 `gorm:"column:white_player_id"`
	BlackId int64 `gorm:"column:black_player_id"`

	Reason string `gorm:"column:reason"`
	Status string `gorm:"column:status"`

	CommonDBFields
}

type GameWithDetails struct {
	Game
	WhitePlayer Player
	BlackPlayer Player
}

func (c GameWithDetails) TableName() string {
	return "games"
}

type GameStatus string

const (
	GameScheduled GameStatus = "scheduled"
	GameDraw      GameStatus = "draw"
	GameBlackWins GameStatus = "black_wins"
	GameWhiteWins GameStatus = "white_wins"
)

func (s GameStatus) String() string {
	return string(s)
}

type GameResult struct {
	White         Player
	Black         Player
	Outcome       game.Outcome
	VictoryReason Victory
	States        []game.State
}
<commit_msg>add converter from api.GameStatus to game.Outcome<commit_after>package api

import (
	"fmt"

	"github.com/danmane/abalone/go/game"
)

type Game struct {
	ID int64 `gorm:"column:id"`

	MatchId int64 `gorm:"column:match_id"`
	WhiteId int64 `gorm:"column:white_player_id"`
	BlackId int64 `gorm:"column:black_player_id"`

	Reason string `gorm:"column:reason"`
	Status string `gorm:"column:status"`

	CommonDBFields
}

func (g *Game) Outcome() (game.Outcome, error) {

	// TODO(btc): Game outcome and game status are somewhat redundant. Look for
	// ways to simplify and merge these enums.

	switch GameStatus(g.Status) {
	// normal cases
	case GameWhiteWins:
		return game.WhiteWins, nil
	case GameBlackWins:
		return game.BlackWins, nil
	case GameDraw:
		return game.Tie, nil

	case GameScheduled:
		// TODO(btc): it is my understanding that NullOutcome is meant to be
		// used for games in progress. Where the DB is concerned, GameScheduled
		// is presently used for games that haven't terminated, so NullOutcome
		// seems like an okay choice for now.
		// We'll probably want to condense/share a single representation. This
		// smells like trouble down the road.
		return game.NullOutcome, nil

	default:
		return 0, fmt.Errorf("unrecognized outcome: %s", g.Status)
	}
}

type GameWithDetails struct {
	Game
	WhitePlayer Player
	BlackPlayer Player
}

func (c GameWithDetails) TableName() string {
	return "games"
}

type GameStatus string

const (
	GameScheduled GameStatus = "scheduled"
	GameDraw      GameStatus = "draw"
	GameBlackWins GameStatus = "black_wins"
	GameWhiteWins GameStatus = "white_wins"
)

func (s GameStatus) String() string {
	return string(s)
}

type GameResult struct {
	White         Player
	Black         Player
	Outcome       game.Outcome
	VictoryReason Victory
	States        []game.State
}
<|endoftext|>


<commit_before>package golcrypt

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"

	"github.com/abhishekkr/gol/golhttpclient"
	"github.com/abhishekkr/gol/golrandom"
)

type Dory struct {
	BaseUrl string
	Backend string //local-auth
	Key     string
	Token   string
}

func (dory *Dory) ShareSecret(value []byte) (err error) {
	request := golhttpclient.HTTPRequest{}

	backend := dory.Backend
	if backend == "" {
		backend = "local-auth"
	}

	key := dory.Key
	if key == "" {
		key = fmt.Sprintf("dory-%s", golrandom.Token(10))
	}

	request.Url = fmt.Sprintf("%s/%s/%s", dory.BaseUrl, backend, key)
	request.Body = bytes.NewBuffer(value)

	dory.Token, err = request.Post()
	return
}

func (dory *Dory) ShareSecretFromFile(filepath string) (err error) {
	requestBody, err := ioutil.ReadFile(filepath)
	if err != nil {
		return
	}

	err = dory.ShareSecret(requestBody)
	return
}

func (dory *Dory) FetchSecret() (value []byte, err error) {
	request := golhttpclient.HTTPRequest{}

	backend := dory.Backend
	if backend == "" {
		backend = "local-auth"
	}

	if dory.Key == "" || dory.Token == "" {
		log.Fatalln("key and token need to be provided to fetch")
	}

	request.Url = fmt.Sprintf("%s/%s/%s", dory.BaseUrl, backend, dory.Key)

	request.HTTPHeaders = map[string]string{
		"X-DORY-TOKEN": dory.Token,
	}

	dory.Token, err = request.Get()
	return
}

func (dory *Dory) RefreshSecret() (value []byte, err error) {
	request := golhttpclient.HTTPRequest{}

	backend := dory.Backend
	if backend == "" {
		backend = "local-auth"
	}

	if dory.Key == "" || dory.Token == "" {
		log.Fatalln("key and token need to be provided to fetch")
	}

	request.Url = fmt.Sprintf("%s/%s/%s", dory.BaseUrl, backend, dory.Key)

	request.GetParams = map[string]string{
		"keep": "true",
	}

	request.HTTPHeaders = map[string]string{
		"X-DORY-TOKEN": dory.Token,
	}

	dory.Token, err = request.Get()
	return
}

func (dory *Dory) PurgeSecret() (err error) {
	request := golhttpclient.HTTPRequest{}

	backend := dory.Backend
	if backend == "" {
		backend = "local-auth"
	}

	key := dory.Key
	if key == "" {
		log.Fatalln("no key provided to purge")
	}

	request.Url = fmt.Sprintf("%s/%s/%s", dory.BaseUrl, backend, dory.Key)

	request.HTTPHeaders = map[string]string{
		"X-DORY-TOKEN": dory.Token,
	}

	dory.Token, err = request.Delete()
	return
}
<commit_msg>[golcrypt] dory to send proper Error not Fatal on missing keys<commit_after>package golcrypt

import (
	"bytes"
	"fmt"
	"io/ioutil"

	"github.com/abhishekkr/gol/golerror"
	"github.com/abhishekkr/gol/golhttpclient"
	"github.com/abhishekkr/gol/golrandom"
)

type Dory struct {
	BaseUrl string
	Backend string //local-auth
	Key     string
	Token   string
}

func (dory *Dory) ShareSecret(value []byte) (err error) {
	request := golhttpclient.HTTPRequest{}

	backend := dory.Backend
	if backend == "" {
		backend = "local-auth"
	}

	key := dory.Key
	if key == "" {
		key = fmt.Sprintf("dory-%s", golrandom.Token(10))
	}

	request.Url = fmt.Sprintf("%s/%s/%s", dory.BaseUrl, backend, key)
	request.Body = bytes.NewBuffer(value)

	dory.Token, err = request.Post()
	return
}

func (dory *Dory) ShareSecretFromFile(filepath string) (err error) {
	requestBody, err := ioutil.ReadFile(filepath)
	if err != nil {
		return
	}

	err = dory.ShareSecret(requestBody)
	return
}

func (dory *Dory) FetchSecret() (value []byte, err error) {
	request := golhttpclient.HTTPRequest{}

	backend := dory.Backend
	if backend == "" {
		backend = "local-auth"
	}

	if dory.Key == "" || dory.Token == "" {
		err = golerror.Error(123, "key or token can't be empty")
		return
	}

	request.Url = fmt.Sprintf("%s/%s/%s", dory.BaseUrl, backend, dory.Key)

	request.HTTPHeaders = map[string]string{
		"X-DORY-TOKEN": dory.Token,
	}

	dory.Token, err = request.Get()
	return
}

func (dory *Dory) RefreshSecret() (value []byte, err error) {
	request := golhttpclient.HTTPRequest{}

	backend := dory.Backend
	if backend == "" {
		backend = "local-auth"
	}

	if dory.Key == "" || dory.Token == "" {
		err = golerror.Error(123, "key and token need to be provided to fetch")
		return
	}

	request.Url = fmt.Sprintf("%s/%s/%s", dory.BaseUrl, backend, dory.Key)

	request.GetParams = map[string]string{
		"keep": "true",
	}

	request.HTTPHeaders = map[string]string{
		"X-DORY-TOKEN": dory.Token,
	}

	dory.Token, err = request.Get()
	return
}

func (dory *Dory) PurgeSecret() (err error) {
	request := golhttpclient.HTTPRequest{}

	backend := dory.Backend
	if backend == "" {
		backend = "local-auth"
	}

	key := dory.Key
	if key == "" {
		err = golerror.Error(123, "no key provided to purge")
		return
	}

	request.Url = fmt.Sprintf("%s/%s/%s", dory.BaseUrl, backend, dory.Key)

	request.HTTPHeaders = map[string]string{
		"X-DORY-TOKEN": dory.Token,
	}

	dory.Token, err = request.Delete()
	return
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"time"

	"github.com/dghubble/go-twitter/twitter"
)

func RegularTweet(client *twitter.Client, c Calendar) error {
	var mes string
	if time.Now().Hour() < 12 {
		mes = TodayMessage(c)
	} else {
		mes = TomorrowMessage(c)
	}
	if _, _, err := client.Statuses.Update(mes, nil); err != nil {
		return err
	}
	return nil
}

// TODO: そのうち時間指定できたりとかにする
func RegularTweetServer(client *twitter.Client, c Calendar) {
	for {
		time.Sleep(time.Hour)
		if err := RegularTweet(client, c); err != nil {
			log.Println("regular tweet error:", err)
		}
		time.Sleep(time.Hour)
	}
}
<commit_msg>3時間おきのつぶやきにした<commit_after>package main

import (
	"log"
	"time"

	"github.com/dghubble/go-twitter/twitter"
)

func RegularTweet(client *twitter.Client, c Calendar) error {
	var mes string
	if time.Now().Hour() < 12 {
		mes = TodayMessage(c)
	} else {
		mes = TomorrowMessage(c)
	}
	if _, _, err := client.Statuses.Update(mes, nil); err != nil {
		return err
	}
	return nil
}

// TODO: そのうち時間指定できたりとかにする
func RegularTweetServer(client *twitter.Client, c Calendar) {
	for {
		time.Sleep(1.5 * time.Hour)
		if err := RegularTweet(client, c); err != nil {
			log.Println("regular tweet error:", err)
		}
		time.Sleep(1.5 * time.Hour)
	}
}
<|endoftext|>


<commit_before>"20 char       string"
"40 char                           string"
<commit_msg>Another test<commit_after>"20 char       string"
"1234567890123456789"
"12345678901234567890"
"123456789012345678901"
"40 char                           string"
"123456789012345678901234567890123456789"
"1234567890123456789012345678901234567890"
"12345678901234567890123456789012345678901"<|endoftext|>


<commit_before>package gpg

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
	"io"
	"strings"
)

func pathListKeys(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "keys/?$",
		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.ListOperation: b.pathKeyList,
		},
		HelpSynopsis:    pathPolicyHelpSyn,
		HelpDescription: pathPolicyHelpDesc,
	}
}

func pathKeys(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "keys/" + framework.GenericNameRegex("name"),
		Fields: map[string]*framework.FieldSchema{
			"name": {
				Type:        framework.TypeString,
				Description: "Name of the key.",
			},
			"real_name": {
				Type:        framework.TypeString,
				Description: "The real name of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"email": {
				Type:        framework.TypeString,
				Description: "The email of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"comment": {
				Type:        framework.TypeString,
				Description: "The comment of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"key": {
				Type:        framework.TypeString,
				Description: "The ASCII-armored GPG key to use. Only used if generate is false.",
			},
			"exportable": {
				Type:        framework.TypeBool,
				Description: "Enables the key to be exportable.",
			},
			"generate": {
				Type:        framework.TypeBool,
				Default:     true,
				Description: "Determines if a key should be generated by Vault or if a key is being passed from another service.",
			},
		},
		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.ReadOperation:   b.pathKeyRead,
			logical.UpdateOperation: b.pathKeyCreate,
			logical.DeleteOperation: b.pathKeyDelete,
		},
		HelpSynopsis:    pathPolicyHelpSyn,
		HelpDescription: pathPolicyHelpDesc,
	}
}

func (b *backend) key(s logical.Storage, name string) (*keyEntry, error) {
	entry, err := s.Get("key/" + name)
	if err != nil {
		return nil, err
	}
	if entry == nil {
		return nil, nil
	}

	var result keyEntry
	if err := entry.DecodeJSON(&result); err != nil {
		return nil, err
	}

	return &result, nil
}

func (b *backend) entity(entry *keyEntry) (*openpgp.Entity, error) {
	r := bytes.NewReader(entry.SerializedKey)
	el, err := openpgp.ReadKeyRing(r)
	if err != nil {
		return nil, err
	}
	if len(el) == 0 {
		return nil, fmt.Errorf("PGP key does not contain an entity")
	}

	return el[0], nil
}

func serializePrivateWithoutSigning(w io.Writer, e *openpgp.Entity) (err error) {
	foundPrivateKey := false

	if e.PrivateKey != nil {
		foundPrivateKey = true
		err = e.PrivateKey.Serialize(w)
		if err != nil {
			return
		}
	}
	for _, ident := range e.Identities {
		err = ident.UserId.Serialize(w)
		if err != nil {
			return
		}
		err = ident.SelfSignature.Serialize(w)
		if err != nil {
			return
		}
	}
	for _, subkey := range e.Subkeys {
		if subkey.PrivateKey != nil {
			foundPrivateKey = true
			err = subkey.PrivateKey.Serialize(w)
			if err != nil {
				return
			}
		}
		err = subkey.Sig.Serialize(w)
		if err != nil {
			return
		}
	}

	if !foundPrivateKey {
		return fmt.Errorf("No private key has been found")
	}

	return nil
}

func (b *backend) pathKeyRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	entry, err := b.key(req.Storage, data.Get("name").(string))
	if err != nil {
		return nil, err
	}
	if entry == nil {
		return nil, nil
	}
	entity, err := b.entity(entry)
	if err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	w, err := armor.Encode(&buf, openpgp.PublicKeyType, nil)
	err = entity.Serialize(w)
	w.Close()
	if err != nil {
		return nil, err
	}

	return &logical.Response{
		Data: map[string]interface{}{
			"fingerprint": hex.EncodeToString(entity.PrimaryKey.Fingerprint[:]),
			"public_key":  buf.String(),
			"exportable":  entry.Exportable,
		},
	}, nil
}

func (b *backend) pathKeyCreate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("name").(string)
	realName := data.Get("real_name").(string)
	email := data.Get("email").(string)
	comment := data.Get("comment").(string)
	exportable := data.Get("exportable").(bool)
	generate := data.Get("generate").(bool)
	key := data.Get("key").(string)

	var buf bytes.Buffer
	switch generate {
	case true:
		entity, err := openpgp.NewEntity(realName, comment, email, nil)
		if err != nil {
			return nil, err
		}
		err = entity.SerializePrivate(&buf, nil)
		if err != nil {
			return nil, err
		}
	default:
		if key == "" {
			return logical.ErrorResponse("the key value is required for generated keys"), nil
		}
		el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key))
		if err != nil {
			return logical.ErrorResponse(err.Error()), nil
		}
		err = serializePrivateWithoutSigning(&buf, el[0])
		if err != nil {
			return logical.ErrorResponse("the key could not be serialized, is a private key present?"), nil
		}
	}

	entry, err := logical.StorageEntryJSON("key/"+name, &keyEntry{
		SerializedKey: buf.Bytes(),
		Exportable:    exportable,
	})
	if err != nil {
		return nil, err
	}
	if err := req.Storage.Put(entry); err != nil {
		return nil, err
	}
	return nil, nil
}

func (b *backend) pathKeyDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	err := req.Storage.Delete("key/" + data.Get("name").(string))
	if err != nil {
		return nil, err
	}
	return nil, nil
}

func (b *backend) pathKeyList(
	req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	entries, err := req.Storage.List("key/")
	if err != nil {
		return nil, err
	}
	return logical.ListResponse(entries), nil
}

type keyEntry struct {
	SerializedKey []byte
	Exportable    bool
}

const pathPolicyHelpSyn = "Managed named GPG keys"
const pathPolicyHelpDesc = `
This path is used to manage the named GPG keys that are available.
Doing a write with no value against a new named key will create
it using a randomly generated key.
`
<commit_msg>Remove useless error when opening an entry<commit_after>package gpg

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"github.com/hashicorp/vault/logical"
	"github.com/hashicorp/vault/logical/framework"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
	"io"
	"strings"
)

func pathListKeys(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "keys/?$",
		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.ListOperation: b.pathKeyList,
		},
		HelpSynopsis:    pathPolicyHelpSyn,
		HelpDescription: pathPolicyHelpDesc,
	}
}

func pathKeys(b *backend) *framework.Path {
	return &framework.Path{
		Pattern: "keys/" + framework.GenericNameRegex("name"),
		Fields: map[string]*framework.FieldSchema{
			"name": {
				Type:        framework.TypeString,
				Description: "Name of the key.",
			},
			"real_name": {
				Type:        framework.TypeString,
				Description: "The real name of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"email": {
				Type:        framework.TypeString,
				Description: "The email of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"comment": {
				Type:        framework.TypeString,
				Description: "The comment of the identity associated with the generated GPG key. Must not contain any of \"()<>\x00\". Only used if generate is false.",
			},
			"key": {
				Type:        framework.TypeString,
				Description: "The ASCII-armored GPG key to use. Only used if generate is false.",
			},
			"exportable": {
				Type:        framework.TypeBool,
				Description: "Enables the key to be exportable.",
			},
			"generate": {
				Type:        framework.TypeBool,
				Default:     true,
				Description: "Determines if a key should be generated by Vault or if a key is being passed from another service.",
			},
		},
		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.ReadOperation:   b.pathKeyRead,
			logical.UpdateOperation: b.pathKeyCreate,
			logical.DeleteOperation: b.pathKeyDelete,
		},
		HelpSynopsis:    pathPolicyHelpSyn,
		HelpDescription: pathPolicyHelpDesc,
	}
}

func (b *backend) key(s logical.Storage, name string) (*keyEntry, error) {
	entry, err := s.Get("key/" + name)
	if err != nil {
		return nil, err
	}
	if entry == nil {
		return nil, nil
	}

	var result keyEntry
	if err := entry.DecodeJSON(&result); err != nil {
		return nil, err
	}

	return &result, nil
}

func (b *backend) entity(entry *keyEntry) (*openpgp.Entity, error) {
	r := bytes.NewReader(entry.SerializedKey)
	el, err := openpgp.ReadKeyRing(r)
	if err != nil {
		return nil, err
	}

	return el[0], nil
}

func serializePrivateWithoutSigning(w io.Writer, e *openpgp.Entity) (err error) {
	foundPrivateKey := false

	if e.PrivateKey != nil {
		foundPrivateKey = true
		err = e.PrivateKey.Serialize(w)
		if err != nil {
			return
		}
	}
	for _, ident := range e.Identities {
		err = ident.UserId.Serialize(w)
		if err != nil {
			return
		}
		err = ident.SelfSignature.Serialize(w)
		if err != nil {
			return
		}
	}
	for _, subkey := range e.Subkeys {
		if subkey.PrivateKey != nil {
			foundPrivateKey = true
			err = subkey.PrivateKey.Serialize(w)
			if err != nil {
				return
			}
		}
		err = subkey.Sig.Serialize(w)
		if err != nil {
			return
		}
	}

	if !foundPrivateKey {
		return fmt.Errorf("No private key has been found")
	}

	return nil
}

func (b *backend) pathKeyRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	entry, err := b.key(req.Storage, data.Get("name").(string))
	if err != nil {
		return nil, err
	}
	if entry == nil {
		return nil, nil
	}
	entity, err := b.entity(entry)
	if err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	w, err := armor.Encode(&buf, openpgp.PublicKeyType, nil)
	err = entity.Serialize(w)
	w.Close()
	if err != nil {
		return nil, err
	}

	return &logical.Response{
		Data: map[string]interface{}{
			"fingerprint": hex.EncodeToString(entity.PrimaryKey.Fingerprint[:]),
			"public_key":  buf.String(),
			"exportable":  entry.Exportable,
		},
	}, nil
}

func (b *backend) pathKeyCreate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("name").(string)
	realName := data.Get("real_name").(string)
	email := data.Get("email").(string)
	comment := data.Get("comment").(string)
	exportable := data.Get("exportable").(bool)
	generate := data.Get("generate").(bool)
	key := data.Get("key").(string)

	var buf bytes.Buffer
	switch generate {
	case true:
		entity, err := openpgp.NewEntity(realName, comment, email, nil)
		if err != nil {
			return nil, err
		}
		err = entity.SerializePrivate(&buf, nil)
		if err != nil {
			return nil, err
		}
	default:
		if key == "" {
			return logical.ErrorResponse("the key value is required for generated keys"), nil
		}
		el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key))
		if err != nil {
			return logical.ErrorResponse(err.Error()), nil
		}
		err = serializePrivateWithoutSigning(&buf, el[0])
		if err != nil {
			return logical.ErrorResponse("the key could not be serialized, is a private key present?"), nil
		}
	}

	entry, err := logical.StorageEntryJSON("key/"+name, &keyEntry{
		SerializedKey: buf.Bytes(),
		Exportable:    exportable,
	})
	if err != nil {
		return nil, err
	}
	if err := req.Storage.Put(entry); err != nil {
		return nil, err
	}
	return nil, nil
}

func (b *backend) pathKeyDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	err := req.Storage.Delete("key/" + data.Get("name").(string))
	if err != nil {
		return nil, err
	}
	return nil, nil
}

func (b *backend) pathKeyList(
	req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	entries, err := req.Storage.List("key/")
	if err != nil {
		return nil, err
	}
	return logical.ListResponse(entries), nil
}

type keyEntry struct {
	SerializedKey []byte
	Exportable    bool
}

const pathPolicyHelpSyn = "Managed named GPG keys"
const pathPolicyHelpDesc = `
This path is used to manage the named GPG keys that are available.
Doing a write with no value against a new named key will create
it using a randomly generated key.
`
<|endoftext|>