Пример #1
0
func MailView(w http.ResponseWriter, r *http.Request, ctx *Context) (err error) {
	id := ctx.Vars["id"]
	log.LogTrace("Loading Mail <%s> from Mongodb", id)

	//we need a user to sign to
	if ctx.User == nil {
		log.LogTrace("This page requires a login.")
		ctx.Session.AddFlash("This page requires a login.")
		return LoginForm(w, r, ctx)
	}

	m, err := ctx.Ds.Load(id)
	if err == nil {
		ctx.Ds.Messages.Update(
			bson.M{"id": m.Id},
			bson.M{"$set": bson.M{"unread": false}},
		)
		return RenderTemplate("mailbox/_show.html", w, map[string]interface{}{
			"ctx":     ctx,
			"title":   "Mail",
			"message": m,
		})
	} else {
		http.NotFound(w, r)
		return
	}
}
Пример #2
0
func MailAttachment(w http.ResponseWriter, r *http.Request, ctx *Context) (err error) {
	id := ctx.Vars["id"]
	log.LogTrace("Loading Attachment <%s> from Mongodb", id)

	//we need a user to sign to
	if ctx.User == nil {
		log.LogTrace("This page requires a login.")
		ctx.Session.AddFlash("This page requires a login.")
		return LoginForm(w, r, ctx)
	}

	m, err := ctx.Ds.LoadAttachment(id)
	if err != nil {
		return fmt.Errorf("ID provided is invalid: %v", err)
	}

	if len(m.Attachments) > 0 {
		at := m.Attachments[0]

		data, err := base64.StdEncoding.DecodeString(at.Body)
		if err != nil {
			return fmt.Errorf("Cannot decode attachment: %v", err)
		}

		reader := bytes.NewReader(data)
		w.Header().Set("Content-Type", at.ContentType)
		//w.Header().Set("Content-Disposition", "attachment; filename=\""+at.FileName+"\"")
		http.ServeContent(w, r, at.FileName, time.Now(), reader)
		return nil
	} else {
		http.NotFound(w, r)
		return
	}
}
Пример #3
0
// TODO support nested MIME content
func ParseSMTPMessage(m *config.SMTPMessage, hostname string, mimeParser bool) *Message {
	arr := make([]*Path, 0)
	for _, path := range m.To {
		arr = append(arr, PathFromString(path))
	}

	msg := &Message{
		Id:      bson.NewObjectId().Hex(),
		From:    PathFromString(m.From),
		To:      arr,
		Created: time.Now(),
		Ip:      m.Host,
		Unread:  true,
		Starred: false,
	}

	if mimeParser {
		msg.Content = &Content{Size: len(m.Data), Headers: make(map[string][]string, 0), Body: m.Data}
		// Read mail using standard mail package
		if rm, err := mail.ReadMessage(bytes.NewBufferString(m.Data)); err == nil {
			log.LogTrace("Reading Mail Message")
			msg.Content.Size = len(m.Data)
			msg.Content.Headers = rm.Header
			msg.Subject = MimeHeaderDecode(rm.Header.Get("Subject"))

			if mt, p, err := mime.ParseMediaType(rm.Header.Get("Content-Type")); err == nil {
				if strings.HasPrefix(mt, "multipart/") {
					log.LogTrace("Parsing MIME Message")
					MIMEBody := &MIMEBody{Parts: make([]*MIMEPart, 0)}
					if err := ParseMIME(MIMEBody, rm.Body, p["boundary"], msg); err == nil {
						log.LogTrace("Got multiparts %d", len(MIMEBody.Parts))
						msg.MIME = MIMEBody
					}
				} else {
					setMailBody(rm, msg)
				}
			} else {
				setMailBody(rm, msg)
			}
		} else {
			msg.Content.TextBody = m.Data
		}
	} else {
		msg.Content = ContentFromString(m.Data)
	}

	recd := fmt.Sprintf("from %s ([%s]) by %s (Smtpd)\r\n  for <%s>; %s\r\n", m.Helo, m.Host, hostname, msg.Id+"@"+hostname, time.Now().Format(time.RFC1123Z))
	//msg.Content.Headers["Delivered-To"]  = []string{msg.To}
	msg.Content.Headers["Message-ID"] = []string{msg.Id + "@" + hostname}
	msg.Content.Headers["Received"] = []string{recd}
	msg.Content.Headers["Return-Path"] = []string{"<" + m.From + ">"}
	return msg
}
Пример #4
0
func GreyMailFromAdd(w http.ResponseWriter, r *http.Request, ctx *Context) (err error) {
	id := ctx.Vars["id"]
	log.LogTrace("Greylist add mail %s", id)

	//we need a user to sign to
	if ctx.User == nil {
		log.LogWarn("Please login to add to grey list!")
		http.NotFound(w, r)
		return
	}

	// we need a user to be admin
	if ctx.User.IsSuperuser == false {
		http.NotFound(w, r)
		return
	}

	// we need to load email
	m, err := ctx.Ds.Load(id)
	if err != nil {
		log.LogTrace("Greylist mail Id not found %s", id)
		http.NotFound(w, r)
		return
	}

	e := fmt.Sprintf("%s@%s", m.From.Mailbox, m.From.Domain)
	if to, _ := ctx.Ds.IsGreyMail(e, "from"); to == 0 {
		log.LogTrace("Greylist inserting mail %s", e)
		gm := data.GreyMail{
			Id:        bson.NewObjectId(),
			CreatedBy: ctx.User.Id.Hex(),
			CreatedAt: time.Now(),
			IsActive:  true,
			Email:     e,
			Local:     m.From.Mailbox,
			Domain:    m.From.Domain,
			Type:      "from",
		}

		if err = ctx.Ds.Emails.Insert(gm); err != nil {
			log.LogError("Error inserting grey list: %s", err)
			http.NotFound(w, r)
			return
		}

		return
	}

	http.NotFound(w, r)
	return
}
Пример #5
0
// Start() the web server
func Start() {
	addr := fmt.Sprintf("%v:%v", webConfig.Ip4address, webConfig.Ip4port)
	server := &http.Server{
		Addr:         addr,
		Handler:      nil,
		ReadTimeout:  60 * time.Second,
		WriteTimeout: 60 * time.Second,
	}

	// We don't use ListenAndServe because it lacks a way to close the listener
	log.LogInfo("HTTP listening on TCP4 %v", addr)
	var err error
	listener, err = net.Listen("tcp", addr)
	if err != nil {
		log.LogError("HTTP failed to start TCP4 listener: %v", err)
		// TODO More graceful early-shutdown procedure
		panic(err)
	}

	err = server.Serve(listener)
	if shutdown {
		log.LogTrace("HTTP server shutting down on request")
	} else if err != nil {
		log.LogError("HTTP server failed: %v", err)
	}
}
Пример #6
0
// ServeHTTP builds the context and passes onto the real handler
func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Create the context
	ctx, err := NewContext(req)
	if err != nil {
		log.LogError("Failed to create context: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer ctx.Close()

	// Run the handler, grab the error, and report it
	buf := new(httpbuf.Buffer)
	log.LogTrace("Web: %v %v %v %v", parseRemoteAddr(req), req.Proto, req.Method, req.RequestURI)
	err = h(buf, req, ctx)
	if err != nil {
		log.LogError("Error handling %v: %v", req.RequestURI, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Save the session
	if err = ctx.Session.Save(req, buf); err != nil {
		log.LogError("Failed to save session: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Apply the buffered response to the writer
	buf.Apply(w)
}
Пример #7
0
func Stop() {
	log.LogTrace("HTTP shutdown requested")
	shutdown = true
	if listener != nil {
		listener.Close()
	} else {
		log.LogError("HTTP listener was nil during shutdown")
	}
}
Пример #8
0
// openLogFile creates or appends to the logfile passed on commandline
func openLogFile() error {
	// use specified log file
	var err error
	logf, err = os.OpenFile(*logfile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		return fmt.Errorf("Failed to create %v: %v\n", *logfile, err)
	}
	golog.SetOutput(logf)
	log.LogTrace("Opened new logfile")
	return nil
}
Пример #9
0
func Register(w http.ResponseWriter, req *http.Request, ctx *Context) error {
	if ctx.User != nil {
		ctx.Session.AddFlash("Already logged in")
		http.Redirect(w, req, reverse("Mails"), http.StatusSeeOther)
	}

	r := &data.LoginForm{
		Username: req.FormValue("username"),
		Password: req.FormValue("password"),
	}

	if r.Validate() {
		result := &data.User{}
		err := ctx.Ds.Users.Find(bson.M{"username": r.Username}).One(&result)
		if err == nil {
			ctx.Session.AddFlash("User already exists!")
			return RegisterForm(w, req, ctx)
		}

		u := &data.User{
			Id:          bson.NewObjectId(),
			Firstname:   req.FormValue("firstname"),
			Lastname:    req.FormValue("lastname"),
			Email:       req.FormValue("email"),
			Username:    r.Username,
			IsActive:    false,
			JoinedAt:    time.Now(),
			LastLoginIp: ctx.ClientIp,
		}
		u.SetPassword(r.Password)

		if err := ctx.Ds.Users.Insert(u); err != nil {
			ctx.Session.AddFlash("Problem registering user.")
			return RegisterForm(w, req, ctx)
		}

		if u.IsActive {
			//store the user id in the values and redirect to index
			ctx.Session.Values["user"] = u.Id.Hex()
			ctx.Session.AddFlash("Registration successful")
			http.Redirect(w, req, reverse("Mails"), http.StatusSeeOther)
			return nil
		} else {
			log.LogTrace("Registration successful")
			ctx.Session.AddFlash("Registration successful")
			return LoginForm(w, req, ctx)
		}
	} else {
		ctx.Session.AddFlash("Please fill all fields!")
		return RegisterForm(w, req, ctx)
	}

	return fmt.Errorf("Failed to register!")
}
Пример #10
0
// ParseTemplate loads the requested template along with _base.html, caching
// the result (if configured to do so)
func ParseTemplate(name string, partial bool) (*template.Template, error) {
	cachedMutex.Lock()
	defer cachedMutex.Unlock()

	if t, ok := cachedTemplates[name]; ok {
		return t, nil
	}

	tempPath := strings.Replace(name, "/", string(filepath.Separator), -1)
	tempFile := filepath.Join(webConfig.TemplateDir, tempPath)
	log.LogTrace("Parsing template %v", tempFile)

	var err error
	var t *template.Template
	if partial {
		// Need to get basename of file to make it root template w/ funcs
		base := path.Base(name)
		t = template.New(base).Funcs(TemplateFuncs)
		t, err = t.ParseFiles(tempFile)
	} else {
		t = template.New("layout.html").Funcs(TemplateFuncs)
		// Note that the layout file must be the first parameter in ParseFiles
		t, err = t.ParseFiles(filepath.Join(webConfig.TemplateDir, "layout.html"), tempFile)
	}
	if err != nil {
		return nil, err
	}

	// Allows us to disable caching for theme development
	if webConfig.TemplateCache {
		if partial {
			log.LogTrace("Caching partial %v", name)
			cachedTemplates[name] = t
		} else {
			log.LogTrace("Caching template %v", name)
			cachedTemplates[name] = t
		}
	}

	return t, nil
}
Пример #11
0
// If running Nginx as a proxy, give Nginx the IP address and port for the SMTP server
// Primary use of Nginx is to terminate TLS so that Go doesn't need to deal with it.
// This could perform auth and load balancing too
// See http://wiki.nginx.org/MailCoreModule
func NginxHTTPAuth(w http.ResponseWriter, r *http.Request, ctx *Context) error {
	//log.LogTrace("Nginx Auth Client: %s", parseRemoteAddr(r))
	log.LogTrace("Nginx Auth Client IP <%s> (%s)", r.Header.Get("Client-IP"), r.Header.Get("Client-Host"))

	cfg := config.GetSmtpConfig()

	w.Header().Add("Auth-Status", "OK")
	w.Header().Add("Auth-Server", cfg.Ip4address.String())
	w.Header().Add("Auth-Port", strconv.Itoa(cfg.Ip4port))
	fmt.Fprint(w, "")
	return nil
}
Пример #12
0
func MailList(w http.ResponseWriter, r *http.Request, ctx *Context) (err error) {
	log.LogTrace("Loading Mails from Mongodb")

	page, _ := strconv.Atoi(ctx.Vars["page"])
	limit := 25

	//we need a user to sign to
	if ctx.User == nil {
		log.LogTrace("This page requires a login.")
		ctx.Session.AddFlash("This page requires a login.")
		return LoginForm(w, r, ctx)
	}

	t, err := ctx.Ds.Total()
	if err != nil {
		http.NotFound(w, r)
		return
	}

	p := NewPagination(t, limit, page, "/mails")
	if page > p.Pages() {
		http.NotFound(w, r)
		return
	}

	messages, err := ctx.Ds.List(p.Offset(), p.Limit())
	if err == nil {
		return RenderTemplate("mailbox/_list.html", w, map[string]interface{}{
			"ctx":        ctx,
			"title":      "Mails",
			"messages":   messages,
			"end":        p.Offset() + p.Limit(),
			"pagination": p,
		})
	} else {
		http.NotFound(w, r)
		return
	}
}
Пример #13
0
func Login(w http.ResponseWriter, req *http.Request, ctx *Context) error {
	l := &data.LoginForm{
		Username: req.FormValue("username"),
		Password: req.FormValue("password"),
	}

	if l.Validate() {
		u, err := ctx.Ds.Login(l.Username, l.Password)

		if err == nil {
			//store the user id in the values and redirect to index
			log.LogTrace("Login successful for session <%v>", u.Id)
			ctx.Ds.Users.Update(
				bson.M{"_id": u.Id},
				bson.M{"$set": bson.M{"lastlogintime": time.Now(), "lastloginip": ctx.ClientIp}, "$inc": bson.M{"logincount": 1}},
			)

			if u.IsActive {
				ctx.Session.Values["user"] = u.Id.Hex()
				http.Redirect(w, req, reverse("Mails"), http.StatusSeeOther)
				return nil
			} else {
				log.LogTrace("The user is not activated")
				ctx.Session.AddFlash("Username is not activated")
				return LoginForm(w, req, ctx)
			}
		} else {
			log.LogTrace("Invalid Username/Password")
			ctx.Session.AddFlash("Invalid Username/Password")
			return LoginForm(w, req, ctx)
		}
	} else {
		ctx.Session.AddFlash("Please fill all fields!")
		return LoginForm(w, req, ctx)
	}

	return fmt.Errorf("Failed to login!")
}
Пример #14
0
func MailDelete(w http.ResponseWriter, r *http.Request, ctx *Context) (err error) {
	id := ctx.Vars["id"]
	log.LogTrace("Delete Mail <%s> from Mongodb", id)

	//we need a user to sign to
	if ctx.User == nil {
		log.LogTrace("This page requires a login.")
		ctx.Session.AddFlash("This page requires a login.")
		return LoginForm(w, r, ctx)
	}

	err = ctx.Ds.DeleteOne(id)
	if err == nil {
		log.LogTrace("Deleted mail id: %s", id)
		ctx.Session.AddFlash("Successfuly deleted mail id:" + id)
		http.Redirect(w, r, reverse("Mails"), http.StatusSeeOther)
		return nil
	} else {
		http.NotFound(w, r)
		return err
	}

}
Пример #15
0
func Status(w http.ResponseWriter, r *http.Request, ctx *Context) (err error) {
	//we need a user to sign to
	if ctx.User == nil {
		log.LogTrace("This page requires a login.")
		ctx.Session.AddFlash("This page requires a login.")
		return LoginForm(w, r, ctx)
	}

	updateSystemStatus()

	return RenderTemplate("root/status.html", w, map[string]interface{}{
		"ctx":       ctx,
		"SysStatus": sysStatus,
	})
}
Пример #16
0
func (ds *DataStore) SaveMail() {
	log.LogTrace("Running SaveMail Rotuines")
	var err error
	var recon bool

	for {
		mc := <-ds.SaveMailChan
		msg := ParseSMTPMessage(mc, mc.Domain, ds.Config.MimeParser)

		if ds.Config.Storage == "mongodb" {
			mc.Hash, err = ds.Storage.(*MongoDB).Store(msg)

			// if mongo conection is broken, try to reconnect only once
			if err == io.EOF && !recon {
				log.LogWarn("Connection error trying to reconnect")
				ds.Storage = CreateMongoDB(ds.Config)
				recon = true

				//try to save again
				mc.Hash, err = ds.Storage.(*MongoDB).Store(msg)
			}

			if err == nil {
				recon = false
				log.LogTrace("Save Mail Client hash : <%s>", mc.Hash)
				mc.Notify <- 1

				//Notify web socket
				ds.NotifyMailChan <- mc.Hash
			} else {
				mc.Notify <- -1
				log.LogError("Error storing message: %s", err)
			}
		}
	}
}
Пример #17
0
func CreateMongoDB(c config.DataStoreConfig) *MongoDB {
	log.LogTrace("Connecting to MongoDB: %s\n", c.MongoUri)

	session, err := mgo.Dial(c.MongoUri)
	if err != nil {
		log.LogError("Error connecting to MongoDB: %s", err)
		return nil
	}

	return &MongoDB{
		Config:   c,
		Session:  session,
		Messages: session.DB(c.MongoDb).C(c.MongoColl),
		Users:    session.DB(c.MongoDb).C("Users"),
		Hosts:    session.DB(c.MongoDb).C("GreyHosts"),
		Emails:   session.DB(c.MongoDb).C("GreyMails"),
		Spamdb:   session.DB(c.MongoDb).C("SpamDB"),
	}
}
Пример #18
0
func (c *Client) tlsHandler() {
	if c.tls_on {
		c.Write("502", "Already running in TLS")
		return
	}

	if c.server.TLSConfig == nil {
		c.Write("502", "TLS not supported")
		return
	}

	log.LogTrace("Ready to start TLS")
	c.Write("220", "Ready to start TLS")

	// upgrade to TLS
	var tlsConn *tls.Conn
	tlsConn = tls.Server(c.conn, c.server.TLSConfig)
	err := tlsConn.Handshake() // not necessary to call here, but might as well

	if err == nil {
		//c.conn   = net.Conn(tlsConn)
		c.conn = tlsConn
		c.bufin = bufio.NewReader(c.conn)
		c.bufout = bufio.NewWriter(c.conn)
		c.tls_on = true

		// Reset envelope as a new EHLO/HELO is required after STARTTLS
		c.reset()

		// Reset deadlines on the underlying connection before I replace it
		// with a TLS connection
		c.conn.SetDeadline(time.Time{})
		c.flush()
	} else {
		c.logWarn("Could not TLS handshake:%v", err)
		c.Write("550", "Handshake error")
	}

	c.state = 1
}
Пример #19
0
func ContentFromString(data string) *Content {
	log.LogTrace("Parsing Content from string: <%d>", len(data))
	x := strings.SplitN(data, "\r\n\r\n", 2)
	h := make(map[string][]string, 0)

	if len(x) == 2 {
		headers, body := x[0], x[1]
		hdrs := strings.Split(headers, "\r\n")
		var lastHdr = ""
		for _, hdr := range hdrs {
			if lastHdr != "" && strings.HasPrefix(hdr, " ") {
				h[lastHdr][len(h[lastHdr])-1] = h[lastHdr][len(h[lastHdr])-1] + hdr
			} else if strings.Contains(hdr, ": ") {
				y := strings.SplitN(hdr, ": ", 2)
				key, value := y[0], y[1]
				// TODO multiple header fields
				h[key] = []string{value}
				lastHdr = key
			} else {
				log.LogWarn("Found invalid header: '%s'", hdr)
			}
		}
		//log.LogTrace("Found body: '%s'", body)
		return &Content{
			Size:    len(data),
			Headers: h,
			Body:    body,
			//Body:   "",
		}
	} else {
		return &Content{
			Size:     len(data),
			Headers:  h,
			Body:     x[0],
			TextBody: x[0],
		}
	}
}
Пример #20
0
func Ping(w http.ResponseWriter, r *http.Request, ctx *Context) error {
	log.LogTrace("Ping successful")
	fmt.Fprint(w, "OK")
	return nil
}
Пример #21
0
// Main listener loop
func (s *Server) Start() {
	cfg := config.GetSmtpConfig()

	log.LogTrace("Loading the certificate: %s", cfg.PubKey)
	cert, err := tls.LoadX509KeyPair(cfg.PubKey, cfg.PrvKey)

	if err != nil {
		log.LogError("There was a problem with loading the certificate: %s", err)
	} else {
		s.TLSConfig = &tls.Config{
			Certificates: []tls.Certificate{cert},
			ClientAuth:   tls.VerifyClientCertIfGiven,
			ServerName:   cfg.Domain,
		}
		//s.TLSConfig  .Rand = rand.Reader
	}

	defer s.Stop()
	addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%v:%v", cfg.Ip4address, cfg.Ip4port))
	if err != nil {
		log.LogError("Failed to build tcp4 address: %v", err)
		// TODO More graceful early-shutdown procedure
		//panic(err)
		s.Stop()
		return
	}

	// Start listening for SMTP connections
	log.LogInfo("SMTP listening on TCP4 %v", addr)
	s.listener, err = net.ListenTCP("tcp4", addr)
	if err != nil {
		log.LogError("SMTP failed to start tcp4 listener: %v", err)
		// TODO More graceful early-shutdown procedure
		//panic(err)
		s.Stop()
		return
	}

	//Connect database
	s.Store.StorageConnect()

	var tempDelay time.Duration
	var clientId int64

	// Handle incoming connections
	for clientId = 1; ; clientId++ {
		if conn, err := s.listener.Accept(); err != nil {
			if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
				// Temporary error, sleep for a bit and try again
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				log.LogError("SMTP accept error: %v; retrying in %v", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			} else {
				if s.shutdown {
					log.LogTrace("SMTP listener shutting down on request")
					return
				}
				// TODO Implement a max error counter before shutdown?
				// or maybe attempt to restart smtpd
				panic(err)
			}
		} else {
			tempDelay = 0
			s.waitgroup.Add(1)
			log.LogInfo("There are now %s serving goroutines", strconv.Itoa(runtime.NumGoroutine()))
			host, _, _ := net.SplitHostPort(conn.RemoteAddr().String())

			s.sem <- 1 // Wait for active queue to drain.
			go s.handleClient(&Client{
				state:      1,
				server:     s,
				conn:       conn,
				remoteHost: host,
				time:       time.Now().Unix(),
				bufin:      bufio.NewReader(conn),
				bufout:     bufio.NewWriter(conn),
				id:         clientId,
			})
		}
	}
}
Пример #22
0
// Stop requests the SMTP server closes it's listener
func (s *Server) Stop() {
	log.LogTrace("SMTP shutdown requested, connections will be drained")
	s.shutdown = true
	s.listener.Close()
}
Пример #23
0
// closeLogFile closes the current logfile
func closeLogFile() error {
	log.LogTrace("Closing logfile")
	return logf.Close()
}
Пример #24
0
// Drain causes the caller to block until all active SMTP sessions have finished
func (s *Server) Drain() {
	s.waitgroup.Wait()
	log.LogTrace("SMTP connections drained")
}
Пример #25
0
// Session specific logging methods
func (c *Client) logTrace(msg string, args ...interface{}) {
	log.LogTrace("SMTP[%v]<%v> %v", c.remoteHost, c.id, fmt.Sprintf(msg, args...))
}
Пример #26
0
func ParseMIME(MIMEBody *MIMEBody, reader io.Reader, boundary string, message *Message) error {
	mr := multipart.NewReader(reader, boundary)

	for {
		mrp, err := mr.NextPart()
		if err != nil {
			if err == io.EOF {
				// This is a clean end-of-message signal
				break
				//log.Fatal("Error eof %s", err)
			}
			return err
		}

		if len(mrp.Header) == 0 {
			// Empty header probably means the part didn't using the correct trailing "--"
			// syntax to close its boundary.  We will let this slide if this this the
			// last MIME part.
			if _, err := mr.NextPart(); err != nil {
				if err == io.EOF || strings.HasSuffix(err.Error(), "EOF") {
					// This is what we were hoping for
					break
				} else {
					return fmt.Errorf("Error at boundary %v: %v", boundary, err)
				}
			}

			return fmt.Errorf("Empty header at boundary %v", boundary)
		}

		ctype := mrp.Header.Get("Content-Type")
		if ctype == "" {
			fmt.Errorf("Missing Content-Type at boundary %v", boundary)
		}

		mediatype, mparams, err := mime.ParseMediaType(ctype)
		if err != nil {
			return err
		}

		encoding := mrp.Header.Get("Content-Transfer-Encoding")
		// Figure out our disposition, filename
		disposition, dparams, err := mime.ParseMediaType(mrp.Header.Get("Content-Disposition"))

		if strings.HasPrefix(mediatype, "multipart/") && mparams["boundary"] != "" {
			// Content is another multipart
			ParseMIME(MIMEBody, mrp, mparams["boundary"], message)
		} else {
			if n, body, err := Partbuf(mrp); err == nil {
				part := &MIMEPart{Size: int(n), Headers: mrp.Header, Body: string(body), FileName: ""}
				// Disposition is optional
				part.Disposition = disposition
				part.ContentType = mediatype
				part.TransferEncoding = encoding

				if mparams["charset"] != "" {
					part.Charset = mparams["charset"]
				}

				if disposition == "attachment" || disposition == "inline" {
					//log.LogTrace("Found attachment: '%s'", disposition)
					part.FileName = MimeHeaderDecode(dparams["filename"])

					if part.FileName == "" && mparams["name"] != "" {
						part.FileName = MimeHeaderDecode(mparams["name"])
					}
				}

				// Save attachments
				if disposition == "attachment" && len(part.FileName) > 0 {
					log.LogTrace("Found attachment: '%s'", disposition)
					//db.messages.find({ 'attachments.id': "54200a938b1864264c000005" }, {"attachments.$" : 1})
					attachment := &Attachment{
						Id:               bson.NewObjectId().Hex(),
						Body:             string(body),
						FileName:         part.FileName,
						Charset:          part.Charset,
						ContentType:      mediatype,
						TransferEncoding: encoding,
						Size:             int(n),
					}
					message.Attachments = append(message.Attachments, attachment)
				} else {
					MIMEBody.Parts = append(MIMEBody.Parts, part)
				}

				//use mediatype; ctype will have 'text/plain; charset=UTF-8'
				// attachments might be plain text content, so make sure of it
				if mediatype == "text/plain" && disposition != "attachment" {
					message.Content.TextBody = MimeBodyDecode(string(body), part.Charset, part.TransferEncoding)
				}

				if mediatype == "text/html" && disposition != "attachment" {
					message.Content.HtmlBody = MimeBodyDecode(string(body), part.Charset, part.TransferEncoding)
				}
			} else {
				log.LogError("Error Processing MIME message: <%s>", err)
			}
		}
	}

	return nil
}