func InitDocumentHandler(defs UploadDefs) { // initialize upload handling parameters uploadPath = defs.Path treshold = defs.ShareTreshold // check for disabled secret sharing scheme if treshold > 0 { // compute prime: (2^512-1) - SharePrimeOfs one := big.NewInt(1) ofs := big.NewInt(int64(defs.SharePrimeOfs)) prime = new(big.Int).Lsh(one, 512) prime = new(big.Int).Sub(prime, one) prime = new(big.Int).Sub(prime, ofs) // open keyring file rdr, err := os.Open(defs.Keyring) if err != nil { // can't read keys -- terminate! logger.Printf(logger.ERROR, "[sid.upload] Can't read keyring file '%s' -- terminating!\n", defs.Keyring) os.Exit(1) } defer rdr.Close() // read public keys from keyring if reviewer, err = openpgp.ReadKeyRing(rdr); err != nil { // can't read keys -- terminate! logger.Printf(logger.ERROR, "[sid.upload] Failed to process keyring '%s' -- terminating!\n", defs.Keyring) os.Exit(1) } } else { logger.Printf(logger.WARN, "[sid.upload] Secret sharing scheme disabled -- uploads will be stored unencrypted!!") } }
// RecvData receives data over network connection (stream-oriented). func RecvData(conn net.Conn, data []byte, srv string) (int, bool) { for retry := 0; retry < retries; { // set timeout conn.SetDeadline(time.Now().Add(timeout)) // read data from socket buffer n, err := conn.Read(data) if err != nil { // handle error condition switch err.(type) { case net.Error: // network error: retry... nerr := err.(net.Error) if nerr.Timeout() { return 0, true } else if nerr.Temporary() { retry++ time.Sleep(delay) continue } default: logger.Printf(logger.INFO, "[%s] Connection closed by peer\n", srv) return 0, false } } // report success if retry > 0 { logger.Printf(logger.INFO, "[%s] %d retries needed to receive data.\n", srv, retry) } return n, true } // retries failed logger.Printf(logger.ERROR, "[%s] Read failed after retries...\n", srv) return 0, false }
// RunService runs a TCP/UDP network service with user-defined // session handler. func RunService(network, addr string, hdlr []Service) error { // initialize control service service, err := net.Listen(network, addr) if err != nil { logger.Println(logger.ERROR, "[network] service start-up failed for '"+network+"/"+addr+"': "+err.Error()) return err } // handle connection requests go func() { for { // wait for connection request client, err := service.Accept() if err != nil { logger.Println(logger.ERROR, "[network] accept failed for '"+network+"/"+addr+"': "+err.Error()) continue } // find service interface that can handle the request accepted := false for _, srv := range hdlr { // check if connection is allowed: remote := client.RemoteAddr().String() protocol := client.RemoteAddr().Network() // check for matching protocol if !srv.CanHandle(protocol) { logger.Printf(logger.WARN, "["+srv.GetName()+"] rejected connection protocol '%s' from %s\n", protocol, remote) continue } // check for matching remote address if !srv.IsAllowed(remote) { logger.Printf(logger.WARN, "["+srv.GetName()+"] rejected connection from %s\n", remote) continue } // connection accepted logger.Printf(logger.INFO, "["+srv.GetName()+"] accepted connection from %s\n", remote) accepted = true // start handler go srv.Process(client) break } // close unhandled connections if !accepted { client.Close() } } }() // report success logger.Println(logger.INFO, "[network] service started on '"+network+"/"+addr+"'...") return nil }
/* * Assemble a HTML header from the current state if there are header * links we need to reproduce. * @param tags *TagList - header tags * @param size int - max size of response * @return string - assembled header */ func (c *Cover) assembleHeader(tags *TagList, size int) string { // add header resources hdr := "<head>\n" for tags.Count() > 0 { // get next tag tag := tags.Get() if tag == nil { break } // translate tag for client inl := c.translateTag(tag) + "\n" // check if we can add the tag? if len(inl) < size { // yes: add it to response hdr += inl size -= len(inl) } else { // no: put it back logger.Printf(logger.WARN, "[sid.cover] can't add all header tags: %d are skipped\n", tags.Count()+1) break } } // close header hdr += "</head>\n" return hdr }
/* * Set target integer to value parsed from string. * @param trgt *int - pointer to target instance * @param data string - string representation of value */ func SetIntValue(trgt *int, data string) { if val, err := strconv.Atoi(data); err == nil { *trgt = val } else { logger.Printf(logger.ERROR, "[sid.config] string conversion from '%s' to integer value failed!", data) } }
// SendData sends data over network connection (stream-oriented). func SendData(conn net.Conn, data []byte, srv string) bool { count := len(data) // total length of data start := 0 // start position of slice retry := 0 // retry counter // write data to socket buffer for count > 0 { // set timeout conn.SetDeadline(time.Now().Add(timeout)) // get (next) chunk to be send chunk := data[start : start+count] if num, err := conn.Write(chunk); num > 0 { // advance slice on partial write start += num count -= num retry = 0 } else if err != nil { // handle error condition switch err.(type) { case net.Error: // network error: retry... nerr := err.(net.Error) if nerr.Timeout() || nerr.Temporary() { retry++ time.Sleep(delay) if retry == retries { logger.Printf(logger.ERROR, "[%s] Write failed after retries: %s\n", srv, err.Error()) return false } } default: logger.Printf(logger.INFO, "[%s] Connection closed by peer\n", srv) return false } } } // report success if retry > 0 { logger.Printf(logger.INFO, "[%s] %d retries needed to send data.\n", srv, retry) } return true }
/* * Translate tag source attribute: if the source specification is an * URI of the form "<scheme>://<server>/<path>/<to>/<resource...>" it * is transformed to an absolute path on on the sending server (that is * the SID instance) that can later be translated back to its original * form; it looks like "/&<scheme>/<server>/<path>/<to>/<resource...>" * @param tag *Tag - tag to be translated * @return string - translated tag */ func (c *Cover) translateTag(tag *Tag) string { if src, ok := tag.attrs["src"]; ok { // translate "src" attribute of tag trgt := translateURI(src) logger.Printf(logger.INFO, "[sid.cover] URI translation of '%s' => '%s'\n", src, trgt) tag.attrs["src"] = trgt } else if src, ok := tag.attrs["href"]; ok { // translate "href" attribute of tag trgt := translateURI(src) logger.Printf(logger.INFO, "[sid.cover] URI translation of '%s' => '%s'\n", src, trgt) tag.attrs["href"] = trgt } else { // failed to access reference attribute?! s := tag.String() logger.Println(logger.ERROR, "[sid.cover] Tag translation failed: "+s) return s } // return tag representation return tag.String() }
/* * Handle callback from parser. * @param mode int - parameter mode * @param param *Parameter - reference to new parameter * @return bool - successful operation? */ func callback(mode int, param *parser.Parameter) bool { // if parameter is specified if param != nil { // print incoming parameter logger.Printf(logger.DBG, "[sid.config] %d: `%s=%s`\n", mode, param.Name, param.Value) if mode != parser.LIST { switch param.Name { case "LogFile": CfgData.LogFile = param.Value case "LogToFile": CfgData.LogState = (param.Value == "ON") case "LogLevel": logger.SetLogLevelFromName(param.Value) case "CrtlPort": SetIntValue(&CfgData.CtrlPort, param.Value) case "CrtlAllow": CfgData.CtrlAllow = param.Value case "HttpPort": SetIntValue(&CfgData.HttpPort, param.Value) case "HttpAllow": CfgData.HttpAllow = param.Value case "UseSocks": CfgData.UseSocks = (param.Value == "ON") case "SocksAddr": CfgData.SocksAddr = param.Value case "Path": CfgData.Upload.Path = param.Value case "Keyring": CfgData.Upload.Keyring = param.Value case "SharePrimeOfs": SetIntValue(&CfgData.Upload.SharePrimeOfs, param.Value) case "ShareTreshold": SetIntValue(&CfgData.Upload.ShareTreshold, param.Value) default: if CustomConfigHandler != nil { return CustomConfigHandler(mode, param) } } } else { if CustomConfigHandler != nil { return CustomConfigHandler(mode, param) } } } return true }
/* * Assemble a HTML body from the current state (like response header), * the resource list and a replacement body (addressed by the requested * resource path from state). * @param s *state - current state info * @param size int - target size of response * @param done bool - can we close the HTML * @return string - assembled HTML body */ func (c *Cover) assembleBody(s *State, size int, done bool) string { // check if requested size can hold HTML wrapper at all. if size < 10 { return "" } // continue HTML body resp := "" // emit pending reponse data first pending := len(s.RespPending) logger.Printf(logger.DBG_ALL, "[sid.cover] assembleBody (%d) -- %d\n", size, pending) switch { case pending > size: resp = string(s.RespPending[0:size]) s.RespPending = string(s.RespPending[size:]) return resp case pending > 0: resp = s.RespPending size -= pending s.RespPending = "" } // add resources (if any are pending) for s.RespTags.Count() > 0 { // get next tag tag := s.RespTags.Get() if tag == nil { break } // translate tag for client inl := c.translateTag(tag) // check if we can add the tag? if len(inl) < size { // yes: add it to response resp += inl size -= len(inl) } else { // no: put it back s.RespTags.Put(tag) break } } return resp }
/* * Initialize image handler: read image definitions from the file * specified by the "defs" argument. * @param defs string - name of XML-based image definitions */ func InitImageHandler() { // prepare parsing of image references imgList = make([]*ImageRef, 0) rdr, err := os.Open(Cfg.ImageDefs) if err != nil { // terminate application in case of failure logger.Println(logger.ERROR, "[images] Can't read image definitions -- terminating!") logger.Println(logger.ERROR, "[images] "+err.Error()) os.Exit(1) } defer rdr.Close() // parse XML file and build image reference list decoder := xml.NewDecoder(rdr) var list ImageList if err = decoder.Decode(&list); err != nil { // terminate application in case of failure logger.Println(logger.ERROR, "[images] Can't decode image definitions -- terminating!") logger.Println(logger.ERROR, "[images] "+err.Error()) os.Exit(1) } for _, img := range list.Image { logger.Println(logger.DBG, "[images]: image="+img.Name) // get size of image file fi, err := os.Stat(img.Path) if err != nil { logger.Println(logger.ERROR, "[images] image '"+img.Path+"' missing!") continue } // clone to reference instance ir := &ImageRef{ name: img.Name, comment: img.Comment, path: img.Path, mime: img.Mime, size: int(fi.Size()), } // add to image list imgList = append(imgList, ir) } logger.Printf(logger.INFO, "[images] %d images available\n", len(imgList)) }
/* * Get list of attributes for a tag. * If the tag is at the end of a HTML fragment and not all attributes * can be read by the tokenizer, this call terminates with a "nil" * map to indicate failure. The tag is than dropped (for an eavesdropper * this looks like a cached resource) * @param tk *html.Tokenizer - tokenizer instance * @return map[string]string - list of attributes */ func getAttrs(tk *html.Tokenizer) (list map[string]string) { // handle panic during parsing defer func() { if r := recover(); r != nil { logger.Printf(logger.WARN, "[sid.html] Skipping fragmented tag: %v\n", r) list = nil } }() // parse attributes from HTML text list = make(map[string]string) for { key, val, more := tk.TagAttr() list[string(key)] = string(val) if !more { break } } return }
/* * Transform cover server response: Substitute absolute URLs in the * response to local links to be handled by the request translations. * @param s *state - reference to state information * @param data []byte - response data from cover server * @param num int - length of response data * @return []data - transformed response (send to client) */ func (c *Cover) xformResp(s *State, data []byte, num int) []byte { // log incoming packet inStr := string(data[0:num]) logger.Printf(logger.DBG_HIGH, "[sid.cover] %d bytes received from cover server.\n", num) logger.Println(logger.DBG_ALL, "[sid.cover] Incoming response:\n"+inStr+"\n") // setup reader and response size := num rdr := bytes.NewBuffer(data[0:num]) resp := "" // initial response package if s.RespMode == 0 { // use identical line break sequence lb := "\r\n" if strings.Index(inStr, lb) == -1 { lb = "\n" } // start of new response encountered: parse header fields hdr: for { // get next line (terminated by line break) line, err := rdr.ReadString('\n') line = strings.TrimRight(line, "\n\r") if err != nil { // header is not complete: wait for next response fragment logger.Println(logger.WARN, "[sid.cover] Response header fragmented!") logger.Println(logger.DBG, "[sid.cover] Assembled response:\n"+resp) if size != len(resp) { logger.Printf(logger.WARN, "[sid.cover] DIFF(response:1) = %d\n", len(resp)-size) } return []byte(resp) } // check if header is available at all.. if strings.HasPrefix(line, "<!") { logger.Println(logger.INFO, "[sid.cover] No response header found: "+line) break hdr } // parse response header switch { //----------------------------------------------------- // Header parsing complete //----------------------------------------------------- case len(line) == 0: // we have parsed the header; continue with body logger.Println(logger.DBG_ALL, "[sid.cover] Incoming response header:\n"+resp) // drop length encoding on gzip content break hdr //----------------------------------------------------- // Status line //----------------------------------------------------- case strings.HasPrefix(line, "HTTP/"): // split line into parts parts := strings.Split(line, " ") status, _ := strconv.Atoi(parts[1]) logger.Printf(logger.DBG, "[sid.cover] response status: %d\n", status) if status != 200 { return data[:size] } //----------------------------------------------------- // Content-Type: //----------------------------------------------------- case strings.HasPrefix(line, "Content-Type: "): // split line into parts parts := strings.Split(line, " ") s.RespType = strings.TrimRight(parts[1], ";") logger.Println(logger.DBG_HIGH, "[sid.cover] response type: "+s.RespType) //----------------------------------------------------- // Content-Encoding: //----------------------------------------------------- case strings.HasPrefix(line, "Content-Encoding: "): // split line into parts parts := strings.Split(line, " ") s.RespEnc = parts[1] logger.Println(logger.DBG_HIGH, "[sid.cover] response encoding: "+s.RespEnc) //----------------------------------------------------- // location: //----------------------------------------------------- case strings.HasPrefix(line, "location: "): // split line into parts parts := strings.Split(line, " ") line = "location: " + translateURI(parts[1]) logger.Println(logger.DBG_HIGH, "[sid.cover] changing location => "+line) } // assemble response resp += line + lb } // add delimiter line resp += lb // adjust remaining content size num -= len(resp) } // start of HTML response? if s.RespMode == 0 { //------------------------------------------------------------- // start HTML response //------------------------------------------------------------- if strings.HasPrefix(s.RespType, "text/html") { // start of a new HTML response. Use pre-defined HTML page // to initialize response. var coverId string = "" s.RespPending, coverId = c.HandleRequest(c, s) s.Data["CoverId"] = coverId } // switch to next mode s.RespMode = 1 } switch { //------------------------------------------------------------- // assemble HTML response //------------------------------------------------------------- case strings.HasPrefix(s.RespType, "text/html"): // do content translation (collect resource tags) done := parseHTML(rdr, s.RespHdr, s.RespTags, s.RespXtra) // sync replacement body (cover content) if response has // been completely processed. if done { c.SyncCover(c, s) } // start of HTML? if s.RespMode == 1 { // initial HTML sequence resp += htmlIntro // output header if available if s.RespHdr.Count() > 0 { hdr := c.assembleHeader(s.RespHdr, num) resp += hdr num -= len(hdr) } // handle HTML body s.RespMode = 2 // open body tag str := "<body>\n" resp += str num -= len(str) } // continue to assemble HTML body body := c.assembleBody(s, num, done) resp += body num -= len(body) if done { // close body and HTML resp += htmlOutro num -= len(htmlOutro) } // we are done with this response packer, but have still response // data to transfer. Fill up with padding sequence. resp += padding(num) // return response data if size != len(resp) { logger.Printf(logger.WARN, "[sid.cover] DIFF(response:2) = %d\n", len(resp)-size) } logger.Println(logger.DBG_ALL, "[sid.cover] Translated response:\n"+resp) return []byte(resp) //------------------------------------------------------------- // Images: Images are considered harmless, so we simply // pass them back to the client. //------------------------------------------------------------- case strings.HasPrefix(s.RespType, "image/"): logger.Println(logger.DBG, "[sid.cover] Image data passed to client") return data[0:size] //------------------------------------------------------------- // JavaScript: Simply replace any JavaScript content with // spaces (looks like the client browser has disabled // JavaScript). //------------------------------------------------------------- case strings.HasPrefix(s.RespType, "application/x-javascript"): // padding to requested size for n := 0; n < num; n++ { resp += " " } // return response data logger.Println(logger.DBG, "[sid.cover] JavaScript scrubbed") if size != len(resp) { logger.Printf(logger.WARN, "[sid.cover] DIFF(response:3) = %d\n", len(resp)-size) } return []byte(resp) //------------------------------------------------------------- // CSS: Simply replace any style sheets with spaces. No image // references in CSS are parsed (looks like those are cached // resources to an eavesdropper) //------------------------------------------------------------- case strings.HasPrefix(s.RespType, "text/css"): // padding to requested size for n := 0; n < num; n++ { resp += " " } // return response data logger.Println(logger.DBG, "[sid.cover] CSS scrubbed") if size != len(resp) { logger.Printf(logger.WARN, "[sid.cover] DIFF(response:4) = %d\n", len(resp)-size) } return []byte(resp) } //return untranslated response logger.Println(logger.ERROR, "[sid.cover] Unhandled response!") return data[0:size] }
/* * Transform client request: this is supposed to work on fragmented * requests if necessary (currently not really supported) * @param s *state - reference to state information * @param data []byte - request data from client * @param num int - length of request in bytes * @return []byte - transformed request (send to cover server) */ func (c *Cover) xformReq(s *State, data []byte, num int) []byte { inStr := string(data[0:num]) logger.Printf(logger.DBG_HIGH, "[sid.cover] %d bytes received from client.\n", num) logger.Println(logger.DBG_ALL, "[sid.cover] Incoming request:\n"+inStr+"\n") // assemble transformed request rdr := bufio.NewReader(strings.NewReader(inStr)) req := "" hasContentEncoding := false // expected content encoding defined? //hasTransferEncoding := false // expected transfer encoding defined? mime := "text/html" // expected content type targetHost := c.Name // request resource from this host (default) balance := 0 // balance between incoming and outgoing information // use identical line break sequence lb := "\r\n" if strings.Index(inStr, lb) == -1 { lb = "\n" } for s.ReqState == RS_HDR { // get next line (terminated by line break) b, broken, _ := rdr.ReadLine() if b == nil || len(b) == 0 { if !broken { s.ReqState = RS_HDR_COMPLETE } break } line := strings.TrimRight(string(b), "\r\n") // transform request data switch { //--------------------------------------------------------- // POST command: upload document // This command triggers the upload of a document to SID // that is covered by an upload to the cover site of the // same length. //--------------------------------------------------------- case strings.HasPrefix(line, "POST "): // split line into parts parts := strings.Split(line, " ") logger.Printf(logger.DBG_HIGH, "[sid.cover] POST '%s'\n", parts[1]) // POST uri encodes the key to the cover POST content and the // target POST URL elem := strings.Split(parts[1], "/") s.ReqBoundaryOut = elem[1] uri := "" for i := 2; i < len(elem); i++ { uri += "/" + elem[i] } // try to get pre-defined cover content. if no cover content // has been constructed yet, the 'reqCoverPost' will contain // nil and the content is constructed later when the content // length of the incoming request is known. s.ReqCoverPost = c.GetPostContent(s.ReqBoundaryOut) s.ReqCoverPostPos = 0 // if URI refers to an external host, split into // host reference and resource specification if pos := strings.Index(uri, "://"); pos != -1 { rem := string(uri[pos+3:]) pos = strings.Index(rem, "/") if pos != -1 { targetHost = rem[0:pos] uri = rem[pos:] logger.Printf(logger.INFO, "[sid.cover] URI split: '%s', '%s'\n", targetHost, uri) } else { logger.Printf(logger.WARN, "[sid.cover] URI split failed on '%s'\n", uri) } } else { targetHost = c.Name } // assemble new POST request s.ReqResource = uri req += "POST " + uri + " HTTP/1.0" + lb s.ReqMode = REQ_POST // keep balance balance += (len(parts[1]) - len(uri)) //--------------------------------------------------------- // GET command: request resource // If the requested resource identifier is a translated // entry, we need to translate that back into its original // form. Translated entries start with "/&". // It is assumed, that a "GET" line is one of the first // lines in a request and therefore never fragmented. // N.B.: We also force HTTP/1.0 to ensure that no // chunking is used by the server (easier parsing). //--------------------------------------------------------- case strings.HasPrefix(line, "GET "): // split line into parts parts := strings.Split(line, " ") logger.Printf(logger.DBG_HIGH, "[sid.cover] resource='%s'\n", parts[1]) // perform translation (if required) uri := translateURI(parts[1]) logger.Printf(logger.INFO, "[sid.cover] URI translation: '%s' => '%s'\n", parts[1], uri) // if URI refers to an external host, split into // host reference and resource specification if pos := strings.Index(uri, "://"); pos != -1 { rem := string(uri[pos+3:]) pos = strings.Index(rem, "/") if pos != -1 { targetHost = rem[0:pos] uri = rem[pos:] logger.Printf(logger.INFO, "[sid.cover] URI split: '%s', '%s'\n", targetHost, uri) } else { logger.Printf(logger.WARN, "[sid.cover] URI split failed on '%s'\n", uri) } } else { targetHost = c.Name } // assemble new resource request s.ReqResource = uri req += "GET " + uri + " HTTP/1.0" + lb s.ReqMode = REQ_GET // keep balance balance += (len(parts[1]) - len(uri)) //--------------------------------------------------------- // Host reference: change to hostname of cover server // This translation may leed to unbalanced request sizes; // the balance will be equalled in a later line // It is assumed, that a "Host:" line is one of the first // lines in a request and therefore never fragmented. //--------------------------------------------------------- case strings.HasPrefix(line, "Host: "): // split line into parts parts := strings.Split(line, " ") // replace hostname reference logger.Printf(logger.DBG_HIGH, "[sid.cover] Host replaced with '%s'\n", targetHost) req += "Host: " + targetHost + lb // keep track of balance balance += (len(parts[1]) - len(targetHost)) //--------------------------------------------------------- // try to get balance straight on language header line: // "Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3" //--------------------------------------------------------- //case s.ReqBalance != 0 && strings.HasPrefix (line, "Accept-Language: "): // @@@TODO: Is this the right place to balance the translation? //--------------------------------------------------------- // Acceptable content encoding: we only want plain HTML //--------------------------------------------------------- case strings.HasPrefix(line, "Accept-Encoding: "): // split line into parts parts := strings.Split(line, " ") hasContentEncoding = true if mime == "text/html" && parts[1] != "identity" { // change to identity encoding for HTML pages repl := "Accept-Encoding: identity" balance += len(repl) - len(line) req += repl + lb } else { req += line + lb } /* //--------------------------------------------------------- // Acceptable transfer encoding: we only want no chunking //--------------------------------------------------------- case strings.HasPrefix (line, "Transfer-Encoding: "): // split line into parts parts := strings.Split (line, " ") hasTransferEncoding = true if mime == "text/html" && parts[1] != "identity" { // change to identity transfer for HTML pages repl := "Transfer-Encoding: identity" balance += len(repl) - len(line) req += repl + lb } else { req += line + lb } */ //--------------------------------------------------------- // Expected content type //--------------------------------------------------------- case strings.HasPrefix(line, "Content-Type: "): // split line into parts parts := strings.Split(line, " ") mime = parts[1] // remember boundary definition if s.ReqMode == REQ_POST { // strip "boundary=" s.ReqBoundaryIn = string(parts[2][9:]) logger.Println(logger.DBG_HIGH, "[sid.cover] Boundary="+s.ReqBoundaryIn) repl := parts[0] + " " + mime + " boundary=---------------------------" + s.ReqBoundaryOut balance += len(repl) - len(line) req += repl + lb } else { req += line + lb } //--------------------------------------------------------- // Referer //--------------------------------------------------------- case strings.HasPrefix(line, "Referer: "): repl := "Referer: " + c.Protocol + "://" + targetHost + "/" balance += len(repl) - len(line) req += repl + lb //--------------------------------------------------------- // Connection //--------------------------------------------------------- case strings.HasPrefix(line, "Connection: "): // split line into parts parts := strings.Split(line, " ") if parts[1] != "close" { repl := "Connection: close" balance += len(repl) - len(line) req += repl + lb } else { req += line + lb } //--------------------------------------------------------- // Keep-Alive: //--------------------------------------------------------- case strings.HasPrefix(line, "Keep-Alive: "): // don't add spec balance -= len(line) //--------------------------------------------------------- // Content-Length //--------------------------------------------------------- case strings.HasPrefix(line, "Content-Length: "): // do we have a pre-defined cover content? if s.ReqCoverPost == nil || s.ReqCoverPost[0] == '!' { // split line into parts parts := strings.Split(line, " ") // get incoming content length s.ReqContentLength, _ = strconv.Atoi(parts[1]) // construct/expand cover content for given size s.ReqCoverPost = c.FinalizeCover(c, s) } // use cover content to construct a content length repl := "Content-Length: " + strconv.Itoa(len(s.ReqCoverPost)) balance += len(repl) - len(line) req += repl + lb //--------------------------------------------------------- // add unchanged request lines. //--------------------------------------------------------- default: req += line if !broken { req += lb } } } // check for completed header in this pass if s.ReqState == RS_HDR_COMPLETE { // add delimiting empty line req += lb // post-process header if mime == "text/html" { if !hasContentEncoding { // enforce identity encoding for HTML pages repl := "Accept-Encoding: identity" balance += len(repl) req += repl + lb } /* if !hasTransferEncoding { // enforce identity transfer for HTML pages repl := "Transfer-Encoding: identity" balance += len(repl) req += repl + lb } */ } if s.ReqMode == REQ_POST { // switch state s.ReqState = RS_CONTENT } else { // we are done s.ReqState = RS_DONE } } // handle processing of request contents for POST requests if s.ReqState == RS_CONTENT { // parse data until end of request for { // get next line (terminated by line break) // and adjust number of bytes read b, _, err := rdr.ReadLine() if err != nil { break } line := strings.TrimRight(string(b), "\r\n") //logger.Println (logger.DBG_ALL, "[sid.cover] POST content: " + line + "\n") if !s.ReqUpload { // check for start of document if strings.Index(line, "name=\"file\";") != -1 { s.ReqUpload = true s.ReqUploadData = "" } } else { if strings.Index(line, s.ReqBoundaryIn) != -1 { s.ReqUpload = false s.ReqUploadOK = PostprocessUploadData([]byte(s.ReqUploadData)) } // we are uploading client data s.ReqUploadData += line + lb } } // build new request data binReq := []byte(req) copy(data, binReq) pos := len(binReq) count := num - pos // we have "count" bytes of response data to sent out start := s.ReqCoverPostPos total := len(s.ReqCoverPost) if start < total { end := start + count s.ReqCoverPostPos = end if end > total { end = total } copy(data[pos:], s.ReqCoverPost[start:end]) pos += (end - start) } // fill up with line breaks if pos < num { fill := "" for count = num - pos; count > 0; count-- { fill += "\n" } data = append(data[0:pos], []byte(fill)...) pos = num } outStr := string(data[0:pos]) logger.Printf(logger.DBG_HIGH, "[sid.cover] %d bytes send to cover server.\n", pos) logger.Println(logger.DBG_ALL, "[sid.cover] Outgoing request:\n"+outStr+"\n") return data[0:pos] } // check for completed request processing if s.ReqState == RS_DONE { if balance != 0 { logger.Printf(logger.WARN, "[sid.cover] Unbalanced request: %d bytes diff\n", balance) } } else { // padding of request with line breaks (if assembled request is smaller; GET only) for num > len(req) && s.ReqMode == REQ_GET { req += "\n" } // return transformed request if num != len(req) { logger.Printf(logger.WARN, "[sid.cover] DIFF(request) = %d\n", len(req)-num) } logger.Printf(logger.DBG_ALL, "[sid.cover] Transformed request:\n"+req+"\n") } return []byte(req) }
/* * Connect to cover server * @return net.Conn - connection to cover server (or nil) */ func (c *Cover) connect() net.Conn { // establish connection directly or via proxy. var ( conn net.Conn err error ) if CfgData.UseSocks { conn, err = network.Socks5Connect("tcp", c.Name, c.Port, CfgData.SocksAddr) if err != nil { // can't connect logger.Printf(logger.ERROR, "[sid.cover] failed to connect to cover server through SOCKS5 proxy: %s\n", err.Error()) return nil } logger.Println(logger.INFO, "[sid.cover] connected to cover server through SOCKS5 proxy...") } else { addr := c.Name + ":" + strconv.Itoa(c.Port) conn, err = net.Dial("tcp", addr) if err != nil { // can't connect logger.Printf(logger.ERROR, "[sid.cover] failed to connect to cover server: %s\n", err.Error()) return nil } logger.Println(logger.INFO, "[sid.cover] directly connected to cover server...") } // allocate state information and add to state list // initialize struct with default data c.States[conn] = &State{ //------------------------------------------------------------- // Request state //------------------------------------------------------------- ReqMode: REQ_UNKNOWN, ReqState: RS_HDR, ReqResource: "", ReqBoundaryIn: "", ReqBoundaryOut: "", ReqCoverPost: nil, ReqCoverPostPos: 0, ReqUpload: false, ReqUploadOK: false, ReqUploadData: "", //------------------------------------------------------------- // Response state //------------------------------------------------------------- RespPending: "", RespEnc: "", RespMode: 0, RespSize: 0, RespType: "text/html", RespHdr: NewTagList(), RespTags: NewTagList(), RespXtra: NewTagList(), //------------------------------------------------------------- // Additional data //------------------------------------------------------------- Data: make(map[string]string), } return conn }
/* * Setup configuration data: Handle SID configuration data and * call custom handler for non-standard configuration data */ func InitConfig() { // process command line arguments CfgData.CfgFile = *flag.String("c", CfgData.CfgFile, "configuration file") flag.String("L", CfgData.LogFile, "logfile name") flag.Bool("l", CfgData.LogState, "file-based logging") flag.Int("p", CfgData.CtrlPort, "control session port") flag.Int("h", CfgData.HttpPort, "HTTP session port") flag.Parse() // read configuration from file logger.Println(logger.INFO, "[sid.config] using configuration file '"+CfgData.CfgFile+"'") cfg, err := os.Open(CfgData.CfgFile) if err != nil { logger.Println(logger.WARN, "[sid.config] configuration file not available -- using defaults") return } // configuration file exists: read parameters rdr := bufio.NewReader(cfg) err = parser.Parser(rdr, callback) if err != nil { logger.Printf(logger.ERROR, "[sid.config] error reading configuration file: %v\n", err) os.Exit(1) } logger.Println(logger.INFO, "[sid.config] configuration file complete.") // handle command line flags that may override options specified in the // configuration file (or are default values) flag.Visit(func(f *flag.Flag) { val := f.Value.String() logger.Printf(logger.INFO, "[sid.config] Overriding '%s' with '%s'\n", f.Usage, val) switch f.Name { case "L": CfgData.LogFile = val case "l": CfgData.LogState = (val == "true") case "p": CfgData.CtrlPort, _ = strconv.Atoi(val) case "h": CfgData.HttpPort, _ = strconv.Atoi(val) } }) // turn on logging if specified on command line or config file if CfgData.LogState { logger.Println(logger.INFO, "[sid.config] File logging requested.") if !logger.LogToFile(CfgData.LogFile) { CfgData.LogState = false } } // set networking parameter network.Delay = 1000000 // 1ms network.Retries = 1000 // max. 1s network.Timeout, _ = time.ParseDuration("100us") // 0.1ms proxy := "<None>" if CfgData.UseSocks { proxy = CfgData.SocksAddr } // list current configuration data logger.Println(logger.INFO, "[sid.config] !==========< configuration >===============") logger.Println(logger.INFO, "[sid.config] ! Configuration file: "+CfgData.CfgFile) logger.Println(logger.INFO, "[sid.config] !Port for control sessions: "+strconv.Itoa(CfgData.CtrlPort)) logger.Println(logger.INFO, "[sid.config] ! Port for HTTP sessions: "+strconv.Itoa(CfgData.HttpPort)) logger.Println(logger.INFO, "[sid.config] ! SOCKS proxy: "+proxy) logger.Println(logger.INFO, "[sid.config] !==========================================") }
// ParseEncrypted parses a encrypted (and possibly signed) message. func ParseEncrypted(ct, addr string, getInfo MailUserInfo, body io.Reader) (*MailContent, error) { mc := new(MailContent) mc.Mode = modeENC boundary := extractValue(ct, "boundary") rdr := multipart.NewReader(body, boundary) for { if part, err := rdr.NextPart(); err == nil { ct = part.Header.Get("Content-Type") switch { case strings.HasPrefix(ct, "application/pgp-encrypted"): buf, err := ioutil.ReadAll(part) if err != nil { return nil, err } logger.Printf(logger.DBG, "application/pgp-encrypted: '%s'\n", strings.TrimSpace(string(buf))) continue case strings.HasPrefix(ct, "application/octet-stream;"): rdr, err := armor.Decode(part) if err != nil { return nil, err } pw := "" pwTmp := getInfo(infoPASSPHRASE, "") switch pwTmp.(type) { case string: pw = pwTmp.(string) } prompt := func(keys []openpgp.Key, symmetric bool) ([]byte, error) { priv := keys[0].PrivateKey if priv.Encrypted { priv.Decrypt([]byte(pw)) } buf := new(bytes.Buffer) priv.Serialize(buf) return buf.Bytes(), nil } id := getIdentity(getInfo, infoIDENTITY, "") md, err := openpgp.ReadMessage(rdr.Body, openpgp.EntityList{id}, prompt, nil) if err != nil { return nil, err } if md.IsSigned { mc.Mode = modeSIGNENC id := getIdentity(getInfo, infoSENDER, addr) if id == nil { mc.Mode = modeUSIGNENC content, err := ioutil.ReadAll(md.UnverifiedBody) if err != nil { return nil, err } mc.Body = string(content) continue } md.SignedBy = crypto.GetKeyFromIdentity(id, crypto.KeySign) md.SignedByKeyId = md.SignedBy.PublicKey.KeyId mc.Key, err = crypto.GetArmoredPublicKey(id) if err != nil { return nil, err } content, err := ioutil.ReadAll(md.UnverifiedBody) if err != nil { return nil, err } if md.SignatureError != nil { return nil, md.SignatureError } logger.Println(logger.INFO, "Signature verified OK") m, err := mail.ReadMessage(bytes.NewBuffer(content)) if err != nil { return nil, err } ct = m.Header.Get("Content-Type") mc2, err := ParsePlain(ct, m.Body) if err != nil { return nil, err } mc.Body = mc2.Body } default: return nil, errors.New("Unhandled MIME part: " + ct) } } else if err == io.EOF { break } else { return nil, err } } return mc, nil }
/* * Client upload data received. * @param data []byte - uploaded document data * @return bool - post-processing successful? */ func PostprocessUploadData(data []byte) bool { logger.Println(logger.INFO, "[sid.upload] Client upload received") logger.Println(logger.DBG_ALL, "[sid.upload] Client upload data:\n"+string(data)) baseName := uploadPath + "/" + CreateId(16) // check if we use a shared secret scheme if reviewer == nil { // no: store content unencrypted. fname := baseName + ".document" wrt, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { logger.Printf(logger.ERROR, "[sid.upload] Can't create document file '%s'\n", fname) return false } // write content and close file wrt.Write(data) wrt.Close() } else { // yes: use shared secret scheme to store upload in encrypted form. var ( err error engine cipher.Block = nil wrt io.WriteCloser = nil ct io.WriteCloser = nil pt io.WriteCloser = nil ) //----------------------------------------------------------------- // setup AES-256 for encryption //----------------------------------------------------------------- key := crypto.RandBytes(32) if engine, err = aes.NewCipher(key); err != nil { // should not happen at all; epic fail if it does logger.Println(logger.ERROR, "[sid.upload] Failed to setup AES cipher!") return false } bs := engine.BlockSize() iv := crypto.RandBytes(bs) enc := cipher.NewCFBEncrypter(engine, iv) logger.Println(logger.DBG_ALL, "[sid.upload] key:\n"+hex.Dump(key)) logger.Println(logger.DBG_ALL, "[sid.upload] IV:\n"+hex.Dump(iv)) //----------------------------------------------------------------- // encrypt client document into file //----------------------------------------------------------------- // open file for output fname := baseName + ".document.aes256" if wrt, err = os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err != nil { logger.Printf(logger.ERROR, "[sid.upload] Can't create document file '%s'\n", fname) return false } // write iv first wrt.Write(iv) // encrypt binary data for the document logger.Println(logger.DBG_ALL, "[sid.upload] AES256 in:\n"+hex.Dump(data)) enc.XORKeyStream(data, data) logger.Println(logger.DBG_ALL, "[sid.upload] AES256 out:\n"+hex.Dump(data)) // write to file wrt.Write(data) wrt.Close() //----------------------------------------------------------------- // create shares from secret //----------------------------------------------------------------- secret := new(big.Int).SetBytes(key) n := len(reviewer) shares := crypto.Split(secret, prime, n, treshold) recipient := make([]*openpgp.Entity, 1) for i, ent := range reviewer { // generate filename based on key id id := strconv.FormatUint(ent.PrimaryKey.KeyId&0xFFFFFFFF, 16) fname = baseName + "." + strings.ToUpper(id) + ".gpg" // create file for output if wrt, err = os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err != nil { logger.Printf(logger.ERROR, "[sid.upload] Can't create share file '%s'\n", fname) continue } // create PGP armorer if ct, err = armor.Encode(wrt, "PGP MESSAGE", nil); err != nil { logger.Printf(logger.ERROR, "[sid.upload] Can't create armorer: %s\n", err.Error()) wrt.Close() continue } // encrypt share to file recipient[0] = ent if pt, err = openpgp.Encrypt(ct, recipient, nil, nil, nil); err != nil { logger.Printf(logger.ERROR, "[sid.upload] Can't create encrypter: %s\n", err.Error()) ct.Close() wrt.Close() continue } pt.Write([]byte(shares[i].P.String() + "\n")) pt.Write([]byte(shares[i].X.String() + "\n")) pt.Write([]byte(shares[i].Y.String() + "\n")) pt.Close() ct.Close() wrt.Close() } } // report success return true }
// Socks5ConnectTimeout connects to a SOCKS5 proxy with timeout. func Socks5ConnectTimeout(proto string, addr string, port int, proxy string, timeout time.Duration) (net.Conn, error) { var ( conn net.Conn err error ) if proto != "tcp" { logger.Printf(logger.ERROR, "[network] Unsupported protocol '%s'.\n", proto) return nil, errors.New("Unsupported protocol (TCP only)") } p, err := url.Parse(proxy) if err != nil { return nil, err } if len(p.Scheme) > 0 && p.Scheme != "socks5" { logger.Printf(logger.ERROR, "[network] Invalid proxy scheme '%s'.\n", p.Scheme) return nil, errors.New("Invalid proxy scheme") } idx := strings.Index(p.Host, ":") if idx == -1 { logger.Printf(logger.ERROR, "[network] Invalid host definition '%s'.\n", p.Host) return nil, errors.New("Invalid host definition (missing port)") } pPort, err := strconv.Atoi(p.Host[idx+1:]) if err != nil || port < 1 || port > 65535 { logger.Printf(logger.ERROR, "[network] Invalid port definition '%d'.\n", pPort) return nil, errors.New("Invalid host definition (port out of range)") } if timeout == 0 { conn, err = net.Dial("tcp", p.Host) } else { conn, err = net.DialTimeout("tcp", p.Host, timeout) } if err != nil { logger.Printf(logger.ERROR, "[network] failed to connect to proxy server: %s\n", err.Error()) return nil, err } data := make([]byte, 1024) //----------------------------------------------------------------- // negotiate authentication //----------------------------------------------------------------- data[0] = 5 // SOCKS version data[1] = 1 // One available authentication method data[2] = 0 // No authentication required if timeout > 0 { conn.SetDeadline(time.Now().Add(timeout)) } if n, err := conn.Write(data[:3]); n != 3 { logger.Printf(logger.ERROR, "[network] failed to write to proxy server: %s\n", err.Error()) conn.Close() return nil, err } if timeout > 0 { conn.SetDeadline(time.Now().Add(timeout)) } if n, err := conn.Read(data); n != 2 { logger.Printf(logger.ERROR, "[network] failed to read from proxy server: %s\n", err.Error()) conn.Close() return nil, err } if data[0] != 5 || data[1] == 0xFF { logger.Println(logger.ERROR, "[network] proxy server refuses non-authenticated connection.") conn.Close() return nil, err } //----------------------------------------------------------------- // connect to target (request/reply processing) //----------------------------------------------------------------- dn := []byte(addr) size := len(dn) data[0] = 5 // SOCKS versions data[1] = 1 // connect to target data[2] = 0 // reserved data[3] = 3 // domain name specified data[4] = byte(size) // length of domain name for i, v := range dn { data[5+i] = v } data[5+size] = (byte)(port / 256) data[6+size] = (byte)(port % 256) if timeout > 0 { conn.SetDeadline(time.Now().Add(timeout)) } if n, err := conn.Write(data[:7+size]); n != (7 + size) { logger.Printf(logger.ERROR, "[network] failed to write to proxy server: %s\n", err.Error()) conn.Close() return nil, err } if timeout > 0 { conn.SetDeadline(time.Now().Add(timeout)) } _, err = conn.Read(data) if err != nil { conn.Close() return nil, err } if data[1] != 0 { err = errors.New(socksState[data[1]]) logger.Printf(logger.ERROR, "[network] proxy server failed: %s\n", err.Error()) conn.Close() return nil, err } //return connection return conn, nil }