コード例 #1
0
ファイル: server.go プロジェクト: jmcarbo/smtpd-1
// 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)
}
コード例 #2
0
ファイル: server.go プロジェクト: jmcarbo/smtpd-1
// 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)
	}
}
コード例 #3
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
//Login validates and returns a user object if they exist in the database.
func (mongo *MongoDB) Login(username, password string) (*User, error) {
	u := &User{}
	err := mongo.Users.Find(bson.M{"username": username}).One(&u)
	if err != nil {
		log.LogError("Login error: %v", err)
		return nil, err
	}

	if ok := Validate_Password(u.Password, password); !ok {
		log.LogError("Invalid Password: %s", u.Username)
		return nil, fmt.Errorf("Invalid Password!")
	}

	return u, nil
}
コード例 #4
0
ファイル: server.go プロジェクト: jmcarbo/smtpd-1
// Debug mail data to file
func (c *Client) saveMailDatatoFile(msg string) {
	filename := fmt.Sprintf("%s/%s-%s-%s.raw", c.server.DebugPath, c.remoteHost, c.from, time.Now().Format("Jan-2-2006-3:04:00pm"))
	f, err := os.Create(filename)

	if err != nil {
		log.LogError("Error saving file %v", err)
	}

	defer f.Close()
	n, err := io.WriteString(f, msg)

	if err != nil {
		log.LogError("Error saving file %v: %v", n, err)
	}
}
コード例 #5
0
ファイル: main.go プロジェクト: hiteshjoshi/smtpd
// signalProcessor is a goroutine that handles OS signals
func signalProcessor(c <-chan os.Signal) {
	for {
		sig := <-c
		switch sig {
		case syscall.SIGHUP:
			// Rotate logs if configured
			if logf != nil {
				log.LogInfo("Recieved SIGHUP, cycling logfile")
				closeLogFile()
				openLogFile()
			} else {
				log.LogInfo("Ignoring SIGHUP, logfile not configured")
			}
		case syscall.SIGTERM:
			// Initiate shutdown
			log.LogInfo("Received SIGTERM, shutting down")
			go timedExit()
			web.Stop()
			if smtpServer != nil {
				smtpServer.Stop()
			} else {
				log.LogError("smtpServer was nil during shutdown")
			}
		}
	}
}
コード例 #6
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) IsGreyMail(email, t string) (int, error) {
	tl, err := mongo.Emails.Find(bson.M{"email": email, "type": t, "isactive": true}).Count()
	if err != nil {
		log.LogError("Error checking email greylist: %s", err)
		return -1, err
	}
	return tl, nil
}
コード例 #7
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) StoreGreyHost(h *GreyHost) (string, error) {
	err := mongo.Hosts.Insert(h)
	if err != nil {
		log.LogError("Error inserting greylist ip: %s", err)
		return "", err
	}
	return h.Id.Hex(), nil
}
コード例 #8
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) IsGreyHost(hostname string) (int, error) {
	tl, err := mongo.Hosts.Find(bson.M{"hostname": hostname, "isactive": true}).Count()
	if err != nil {
		log.LogError("Error checking host greylist: %s", err)
		return -1, err
	}
	return tl, nil
}
コード例 #9
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) Total() (int, error) {
	total, err := mongo.Messages.Find(bson.M{}).Count()
	if err != nil {
		log.LogError("Error loading message: %s", err)
		return -1, err
	}
	return total, nil
}
コード例 #10
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) StoreGreyMail(m *GreyMail) (string, error) {
	err := mongo.Emails.Insert(m)
	if err != nil {
		log.LogError("Error inserting greylist email: %s", err)
		return "", err
	}
	return m.Id.Hex(), nil
}
コード例 #11
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) StoreSpamIp(s SpamIP) (string, error) {
	err := mongo.Spamdb.Insert(s)
	if err != nil {
		log.LogError("Error inserting greylist ip: %s", err)
		return "", err
	}
	return s.Id.Hex(), nil
}
コード例 #12
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) Store(m *Message) (string, error) {
	err := mongo.Messages.Insert(m)
	if err != nil {
		log.LogError("Error inserting message: %s", err)
		return "", err
	}
	return m.Id, nil
}
コード例 #13
0
ファイル: server.go プロジェクト: jmcarbo/smtpd-1
func Stop() {
	log.LogTrace("HTTP shutdown requested")
	shutdown = true
	if listener != nil {
		listener.Close()
	} else {
		log.LogError("HTTP listener was nil during shutdown")
	}
}
コード例 #14
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) Load(id string) (*Message, error) {
	result := &Message{}
	err := mongo.Messages.Find(bson.M{"id": id}).One(&result)
	if err != nil {
		log.LogError("Error loading message: %s", err)
		return nil, err
	}
	return result, nil
}
コード例 #15
0
ファイル: template.go プロジェクト: jmcarbo/smtpd-1
// RenderPartial fetches the named template and renders it to the provided
// ResponseWriter.
func RenderPartial(name string, w http.ResponseWriter, data interface{}) error {
	t, err := ParseTemplate(name, true)
	if err != nil {
		log.LogError("Error in template '%v': %v", name, err)
		return err
	}
	w.Header().Set("Expires", "-1")
	return t.Execute(w, data)
}
コード例 #16
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func getSession(c config.DataStoreConfig) *mgo.Session {
	if mgoSession == nil {
		var err error
		mgoSession, err = mgo.Dial(c.MongoUri)
		if err != nil {
			log.LogError("Session Error connecting to MongoDB: %s", err)
			return nil
		}
	}
	return mgoSession.Clone()
}
コード例 #17
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) LoadAttachment(id string) (*Message, error) {
	result := &Message{}
	err := mongo.Messages.Find(bson.M{"attachments.id": id}).Select(bson.M{
		"id":            1,
		"attachments.$": 1,
	}).One(&result)
	if err != nil {
		log.LogError("Error loading attachment: %s", err)
		return nil, err
	}
	return result, nil
}
コード例 #18
0
ファイル: storage.go プロジェクト: jmcarbo/smtpd-1
func (ds *DataStore) SaveSpamIP(ip string, email string) {
	s := SpamIP{
		Id:        bson.NewObjectId(),
		CreatedAt: time.Now(),
		IsActive:  true,
		Email:     email,
		IPAddress: ip,
	}

	if _, err := ds.Storage.(*MongoDB).StoreSpamIp(s); err != nil {
		log.LogError("Error inserting Spam IPAddress: %s", err)
	}
}
コード例 #19
0
ファイル: handlers.go プロジェクト: jmcarbo/smtpd-1
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
}
コード例 #20
0
ファイル: template.go プロジェクト: jmcarbo/smtpd-1
// Reversable routing function (shared with templates)
func reverse(name string, things ...interface{}) string {
	// Convert the things to strings
	strs := make([]string, len(things))
	for i, th := range things {
		strs[i] = fmt.Sprint(th)
	}
	// Grab the route
	u, err := Router.Get(name).URL(strs...)
	if err != nil {
		log.LogError("Failed to reverse route: %v", err)
		return "/ROUTE-ERROR"
	}
	return u.Path
}
コード例 #21
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
func (mongo *MongoDB) List(start int, limit int) (*Messages, error) {
	messages := &Messages{}
	err := mongo.Messages.Find(bson.M{}).Sort("-_id").Skip(start).Limit(limit).Select(bson.M{
		"id":          1,
		"from":        1,
		"to":          1,
		"attachments": 1,
		"created":     1,
		"ip":          1,
		"subject":     1,
		"starred":     1,
		"unread":      1,
	}).All(messages)
	if err != nil {
		log.LogError("Error loading messages: %s", err)
		return nil, err
	}
	return messages, nil
}
コード例 #22
0
ファイル: mongostore.go プロジェクト: jmcarbo/smtpd-1
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"),
	}
}
コード例 #23
0
ファイル: storage.go プロジェクト: jmcarbo/smtpd-1
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)
			}
		}
	}
}
コード例 #24
0
ファイル: server.go プロジェクト: jmcarbo/smtpd-1
// 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,
			})
		}
	}
}
コード例 #25
0
ファイル: server.go プロジェクト: jmcarbo/smtpd-1
func (c *Client) logError(msg string, args ...interface{}) {
	// Update metrics
	//expErrorsTotal.Add(1)
	log.LogError("SMTP[%v]<%v> %v", c.remoteHost, c.id, fmt.Sprintf(msg, args...))
}
コード例 #26
0
ファイル: message.go プロジェクト: jmcarbo/smtpd-1
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
}
コード例 #27
0
ファイル: main.go プロジェクト: hiteshjoshi/smtpd
// timedExit is called as a goroutine during shutdown, it will force an exit after 15 seconds
func timedExit() {
	time.Sleep(15 * time.Second)
	log.LogError("Smtpd clean shutdown timed out, forcing exit")
	os.Exit(0)
}
コード例 #28
0
ファイル: main.go プロジェクト: hiteshjoshi/smtpd
func main() {

	flag.Parse()
	runtime.GOMAXPROCS(runtime.NumCPU())

	if *help {
		flag.Usage()
		return
	}

	// Load & Parse config
	/*	if flag.NArg() != 1 {
		flag.Usage()
		os.Exit(1)
	}*/

	//err := config.LoadConfig(flag.Arg(0))
	err := config.LoadConfig(*configfile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse config: %v\n", err)
		os.Exit(1)
	}

	// Setup signal handler
	sigChan := make(chan os.Signal)
	signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGTERM)
	go signalProcessor(sigChan)

	// Configure logging, close std* fds
	level, _ := config.Config.String("logging", "level")
	log.SetLogLevel(level)

	if *logfile != "stderr" {
		// stderr is the go logging default
		if *logfile == "stdout" {
			// set to stdout
			golog.SetOutput(os.Stdout)
		} else {
			err := openLogFile()
			if err != nil {
				fmt.Fprintf(os.Stderr, "%v", err)
				os.Exit(1)
			}
			defer closeLogFile()

			// close std* streams
			os.Stdout.Close()
			os.Stderr.Close() // Warning: this will hide panic() output
			os.Stdin.Close()
			os.Stdout = logf
			os.Stderr = logf
		}
	}

	log.LogInfo("Smtpd %v (%v) starting...", VERSION, BUILD_DATE)

	// Write pidfile if requested
	// TODO: Probably supposed to remove pidfile during shutdown
	if *pidfile != "none" {
		pidf, err := os.Create(*pidfile)
		if err != nil {
			log.LogError("Failed to create %v: %v", *pidfile, err)
			os.Exit(1)
		}
		defer pidf.Close()
		fmt.Fprintf(pidf, "%v\n", os.Getpid())
	}

	// Grab our datastore
	ds := data.NewDataStore()

	// Start HTTP server
	//web.Initialize(config.GetWebConfig(), ds)
	//go web.Start()

	// Startup SMTP server, block until it exits
	smtpServer = smtpd.NewSmtpServer(config.GetSmtpConfig(), ds)
	smtpServer.Start()

	// Wait for active connections to finish
	smtpServer.Drain()
}