Example #1
0
func (a *rethinkAPIServer) ListJobInfos(ctx context.Context, request *pps.ListJobRequest) (response *persist.JobInfos, err error) {
	defer func(start time.Time) { a.Log(request, response, err, time.Since(start)) }(time.Now())
	query := a.getTerm(jobInfosTable)
	if request.Pipeline != nil && request.Input != nil {
		query = query.GetAllByIndex(
			pipelineNameAndInputIndex,
			gorethink.Expr([]string{request.Pipeline.Name, request.Input.Repo.Name, request.Input.Id}),
		)
	} else if request.Pipeline != nil {
		query = query.GetAllByIndex(
			pipelineNameIndex,
			request.Pipeline.Name,
		)
	} else if request.Input != nil {
		query = query.GetAllByIndex(
			inputIndex,
			gorethink.Expr([]string{request.Input.Repo.Name, request.Input.Id}),
		)
	}
	jobInfoObjs, err := a.getAllMessages(
		jobInfosTable,
		func() proto.Message { return &persist.JobInfo{} },
	)
	if err != nil {
		return nil, err
	}
	jobInfos := make([]*persist.JobInfo, len(jobInfoObjs))
	for i, jobInfoObj := range jobInfoObjs {
		jobInfos[i] = jobInfoObj.(*persist.JobInfo)
	}
	sortJobInfosByTimestampDesc(jobInfos)
	return &persist.JobInfos{
		JobInfo: jobInfos,
	}, nil
}
Example #2
0
func (f *FilesTable) GetInEmail(owner string, email string, name string) ([]*models.File, error) {
	e, err := f.Emails.GetEmail(email)
	if err != nil {
		return nil, err
	}

	query, err := f.GetTable().Filter(func(row gorethink.Term) gorethink.Term {
		return gorethink.And(
			row.Field("owner").Eq(gorethink.Expr(owner)),
			gorethink.Expr(e.Files).Contains(row.Field("id")),
			row.Field("name").Eq(gorethink.Expr(name)),
		)
	}).Run(f.GetSession())
	if err != nil {
		return nil, err
	}

	var result []*models.File
	err = query.All(&result)
	if err != nil {
		return nil, err
	}

	return result, nil
}
Example #3
0
func (a *rethinkAPIServer) InspectJob(ctx context.Context, request *pps.InspectJobRequest) (response *persist.JobInfo, err error) {
	defer func(start time.Time) { a.Log(request, response, err, time.Since(start)) }(time.Now())
	jobInfo := &persist.JobInfo{}
	var mustHaveFields []interface{}
	if request.BlockOutput {
		mustHaveFields = append(mustHaveFields, "OutputCommit")
	}
	if request.BlockState {
		mustHaveFields = append(mustHaveFields, "State")
	}
	if err := a.waitMessageByPrimaryKey(
		jobInfosTable,
		request.Job.Id,
		jobInfo,
		func(jobInfo gorethink.Term) gorethink.Term {
			blockOutput := jobInfo.HasFields("OutputCommit")
			blockState := jobInfo.Field("State").Ne(pps.JobState_JOB_STATE_RUNNING)
			if request.BlockOutput && request.BlockState {
				return blockOutput.And(blockState)
			} else if request.BlockOutput {
				return blockOutput
			} else if request.BlockState {
				return blockState
			}
			return gorethink.Expr(true)
		},
	); err != nil {
		return nil, err
	}
	return jobInfo, nil
}
func (a *rethinkAPIServer) InspectJob(ctx context.Context, request *ppsclient.InspectJobRequest) (response *persist.JobInfo, err error) {
	defer func(start time.Time) { a.Log(request, response, err, time.Since(start)) }(time.Now())
	if request.Job == nil {
		return nil, fmt.Errorf("request.Job cannot be nil")
	}

	jobInfo := &persist.JobInfo{}
	var mustHaveFields []interface{}
	if request.BlockState {
		mustHaveFields = append(mustHaveFields, "State")
	}
	if err := a.waitMessageByPrimaryKey(
		jobInfosTable,
		request.Job.ID,
		jobInfo,
		func(jobInfo gorethink.Term) gorethink.Term {
			if request.BlockState {
				return gorethink.Or(
					jobInfo.Field("State").Eq(ppsclient.JobState_JOB_SUCCESS),
					jobInfo.Field("State").Eq(ppsclient.JobState_JOB_FAILURE))
			}
			return gorethink.Expr(true)
		},
	); err != nil {
		return nil, err
	}
	return jobInfo, nil
}
func (a *rethinkAPIServer) ListJobInfos(ctx context.Context, request *ppsclient.ListJobRequest) (response *persist.JobInfos, retErr error) {
	defer func(start time.Time) { a.Log(request, response, retErr, time.Since(start)) }(time.Now())
	query := a.getTerm(jobInfosTable)
	commitIndexVal, err := genCommitIndex(request.InputCommit)
	if err != nil {
		return nil, err
	}
	if request.Pipeline != nil && len(request.InputCommit) > 0 {
		query = query.GetAllByIndex(
			pipelineNameAndCommitIndex,
			gorethink.Expr([]interface{}{request.Pipeline.Name, commitIndexVal}),
		)
	} else if request.Pipeline != nil {
		query = query.GetAllByIndex(
			pipelineNameIndex,
			request.Pipeline.Name,
		)
	} else if len(request.InputCommit) > 0 {
		query = query.GetAllByIndex(
			commitIndex,
			gorethink.Expr(commitIndexVal),
		)
	}
	cursor, err := query.Run(a.session)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err := cursor.Close(); err != nil && retErr == nil {
			retErr = err
		}
	}()
	result := &persist.JobInfos{}
	for {
		jobInfo := &persist.JobInfo{}
		if !cursor.Next(jobInfo) {
			break
		}
		result.JobInfo = append(result.JobInfo, jobInfo)
	}
	if err := cursor.Err(); err != nil {
		return nil, err
	}
	return result, nil
}
Example #6
0
func BenchmarkDelete(b *testing.B) {
	for n := 0; n < b.N; n++ {
		_, err := gorethink.Db("test").Table("benchmark_keys_list").Get(table2search).Field("voted").DeleteAt(
			gorethink.Expr(gorethink.Db("test").Table("benchmark_keys_list").Get(table2search).Field("voted").IndexesOf(key2find).AtIndex(0)),
		).Run(session)
		if err != nil {
			b.Log(err)
			b.Fail()
		}
	}
}
Example #7
0
func (f *FilesTable) GetEmailFiles(id string) ([]*models.File, error) {
	email, err := f.Emails.GetEmail(id)
	if err != nil {
		return nil, err
	}

	query, err := f.Emails.GetTable().Filter(func(row gorethink.Term) gorethink.Term {
		return gorethink.Expr(email.Files).Contains(row.Field("id"))
	}).GetAll().Run(f.Emails.GetSession())
	if err != nil {
		return nil, err
	}

	var result []*models.File
	err = query.All(&result)
	if err != nil {
		return nil, err
	}

	return result, nil
}
Example #8
0
func Example() {
	session, err := r.Connect(r.ConnectOpts{
		Address: url,
		AuthKey: authKey,
	})
	if err != nil {
		log.Fatalln(err.Error())
	}

	res, err := r.Expr("Hello World").Run(session)
	if err != nil {
		log.Fatalln(err.Error())
	}

	var response string
	err = res.One(&response)
	if err != nil {
		log.Fatalln(err.Error())
	}

	fmt.Println(response)
}
Example #9
0
func PrepareHandler(config *shared.Flags) func(peer smtpd.Peer, env smtpd.Envelope) error {
	cfg = config

	// Initialize a new logger
	log := logrus.New()
	if config.LogFormatterType == "text" {
		log.Formatter = &logrus.TextFormatter{
			ForceColors: config.ForceColors,
		}
	} else if config.LogFormatterType == "json" {
		log.Formatter = &logrus.JSONFormatter{}
	}

	log.Level = logrus.DebugLevel

	// Initialize the database connection
	var err error
	session, err = gorethink.Connect(gorethink.ConnectOpts{
		Address: config.RethinkAddress,
		AuthKey: config.RethinkKey,
		MaxIdle: 10,
		Timeout: time.Second * 10,
	})
	if err != nil {
		log.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("Unable to connect to RethinkDB")
	}

	// Connect to NSQ
	producer, err := nsq.NewProducer(config.NSQDAddress, nsq.NewConfig())
	if err != nil {
		log.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("Unable to connect to NSQd")
	}

	// Create a new spamd client
	spam := spamc.New(config.SpamdAddress, 10)

	// Last message sent by PrepareHandler
	log.WithFields(logrus.Fields{
		"addr": config.BindAddress,
	}).Info("Listening for incoming traffic")

	return func(peer smtpd.Peer, e smtpd.Envelope) error {
		log.Debug("Started parsing")

		// Check recipients for Lavaboom users
		recipients := []interface{}{}
		for _, recipient := range e.Recipients {
			log.Printf("EMAIL TO %s", recipient)

			// Split the email address into username and domain
			parts := strings.Split(recipient, "@")
			if len(parts) != 2 {
				return describeError(fmt.Errorf("Invalid recipient email address"))
			}

			// Check if we support that domain
			if _, ok := domains[parts[1]]; ok {
				recipients = append(recipients,
					utils.RemoveDots(
						utils.NormalizeUsername(parts[0]),
					),
				)
			}
		}

		log.Debug("Parsed recipients")

		// If we didn't find a recipient, return an error
		if len(recipients) == 0 {
			return describeError(fmt.Errorf("Not supported email domain"))
		}

		// Fetch the mapping
		cursor, err := gorethink.Db(config.RethinkDatabase).Table("addresses").GetAll(recipients...).Run(session)
		if err != nil {
			return describeError(err)
		}
		defer cursor.Close()
		var addresses []*models.Address
		if err := cursor.All(&addresses); err != nil {
			return describeError(err)
		}

		// Transform the mapping into accounts
		accountIDs := []interface{}{}
		for _, address := range addresses {
			accountIDs = append(accountIDs, address.Owner)
		}

		// Fetch accounts
		cursor, err = gorethink.Db(config.RethinkDatabase).Table("accounts").GetAll(accountIDs...).Run(session)
		if err != nil {
			return describeError(err)
		}
		defer cursor.Close()
		var accounts []*models.Account
		if err := cursor.All(&accounts); err != nil {
			return describeError(err)
		}

		// Compare request and result lengths
		if len(accounts) != len(recipients) {
			return describeError(fmt.Errorf("One of the email addresses wasn't found"))
		}

		log.Debug("Recipients found")

		// Prepare a variable for the combined keyring of recipients
		toKeyring := []*openpgp.Entity{}

		// Fetch users' public keys
		for _, account := range accounts {
			account.Key, err = getAccountPublicKey(account)
			if err != nil {
				return describeError(err)
			}

			toKeyring = append(toKeyring, account.Key)
		}

		log.Debug("Fetched keys")

		// Check in the antispam
		isSpam := false
		spamReply, err := spam.Report(string(e.Data))
		if err == nil {
			log.Print(spamReply.Code)
			log.Print(spamReply.Message)
			log.Print(spamReply.Vars)
		}
		if spamReply.Code == spamc.EX_OK {
			log.Print("Proper code")
			if spam, ok := spamReply.Vars["isSpam"]; ok && spam.(bool) {
				log.Print("It's spam.")
				isSpam = true
			}
		}

		// Parse the email
		email, err := ParseEmail(bytes.NewReader(e.Data))
		if err != nil {
			return describeError(err)
		}

		// Determine email's kind
		contentType := email.Headers.Get("Content-Type")
		kind := "raw"
		if strings.HasPrefix(contentType, "multipart/encrypted") {
			// multipart/encrypted is dedicated for PGP/MIME and S/MIME
			kind = "pgpmime"
		} else if strings.HasPrefix(contentType, "multipart/mixed") && len(email.Children) >= 2 {
			// Has manifest? It is an email with a PGP manifest. If not, it's unencrypted.
			for _, child := range email.Children {
				if strings.HasPrefix(child.Headers.Get("Content-Type"), "application/x-pgp-manifest") {
					kind = "manifest"
					break
				}
			}
		}

		// Copy kind to a second variable for later parsing
		initialKind := kind

		// Debug the kind
		log.Debugf("Email is %s", kind)

		// Declare variables used later for data insertion
		var (
			subject  string
			manifest string
			body     string
			fileIDs  = map[string][]string{}
			files    = []*models.File{}
		)

		// Transform raw emails into encrypted with manifests
		if kind == "raw" {
			// Prepare variables for manifest generation
			parts := []*man.Part{}

			// Parsing vars
			var (
				bodyType string
				bodyText string
			)

			// Flatten the email
			var parseBody func(msg *Message) error
			parseBody = func(msg *Message) error {
				contentType := msg.Headers.Get("Content-Type")

				if strings.HasPrefix(contentType, "multipart/alternative") {
					preferredType := ""
					preferredIndex := -1

					// Find the best body
					for index, child := range msg.Children {
						contentType := child.Headers.Get("Content-Type")
						if strings.HasPrefix(contentType, "application/pgp-encrypted") {
							preferredType = "pgp"
							preferredIndex = index
							break
						}

						if strings.HasPrefix(contentType, "text/html") {
							preferredType = "html"
							preferredIndex = index
						}

						if strings.HasPrefix(contentType, "text/plain") {
							if preferredType != "html" {
								preferredType = "plain"
								preferredIndex = index
							}
						}
					}

					if preferredIndex == -1 && len(msg.Children) > 0 {
						preferredIndex = 0
					} else if preferredIndex == -1 {
						return nil // crappy email
					}

					// Parse its media type to remove non-required stuff
					match := msg.Children[preferredIndex]
					mediaType, _, err := mime.ParseMediaType(match.Headers.Get("Content-Type"))
					if err != nil {
						return describeError(err)
					}

					// Push contents into the parser's scope
					bodyType = mediaType
					bodyText = string(match.Body)

					/* change of plans - discard them.
					// Transform rest of the types into attachments
					nodeID := uniuri.New()
					for _, child := range msg.Children {
						child.Headers["disposition"] = "attachment; filename=\"alternative." + nodeID + "." + mime. +"\""
					}*/
				} else if strings.HasPrefix(contentType, "multipart/") {
					// Tread every other multipart as multipart/mixed, as we parse multipart/encrypted later
					for _, child := range msg.Children {
						if err := parseBody(child); err != nil {
							return describeError(err)
						}
					}
				} else {
					// Parse the content type
					mediaType, _, err := mime.ParseMediaType(contentType)
					if err != nil {
						return describeError(err)
					}

					// Not multipart, parse the disposition
					disposition, dparams, err := mime.ParseMediaType(msg.Headers.Get("Content-Disposition"))

					if err == nil && disposition == "attachment" {
						// We're dealing with an attachment
						id := uniuri.NewLen(uniuri.UUIDLen)

						// Encrypt the body
						encryptedBody, err := shared.EncryptAndArmor(msg.Body, toKeyring)
						if err != nil {
							return describeError(err)
						}

						// Hash the body
						rawHash := sha256.Sum256(msg.Body)
						hash := hex.EncodeToString(rawHash[:])

						// Push the attachment into parser's scope
						parts = append(parts, &man.Part{
							Hash:        hash,
							ID:          id,
							ContentType: mediaType,
							Filename:    dparams["filename"],
							Size:        len(msg.Body),
						})

						for _, account := range accounts {
							fid := uniuri.NewLen(uniuri.UUIDLen)

							files = append(files, &models.File{
								Resource: models.Resource{
									ID:           fid,
									DateCreated:  time.Now(),
									DateModified: time.Now(),
									Name:         id + ".pgp",
									Owner:        account.ID,
								},
								Encrypted: models.Encrypted{
									Encoding: "application/pgp-encrypted",
									Data:     string(encryptedBody),
								},
							})

							if _, ok := fileIDs[account.ID]; !ok {
								fileIDs[account.ID] = []string{}
							}

							fileIDs[account.ID] = append(fileIDs[account.ID], fid)
						}
					} else {
						// Header is either corrupted or we're dealing with inline
						if bodyType == "" && mediaType == "text/plain" || mediaType == "text/html" {
							bodyType = mediaType
							bodyText = string(msg.Body)
						} else if bodyType == "" {
							bodyType = "text/html"

							if strings.Index(mediaType, "image/") == 0 {
								bodyText = `<img src="data:` + mediaType + `;base64,` + base64.StdEncoding.EncodeToString(msg.Body) + `"><br>`
							} else {
								bodyText = "<pre>" + string(msg.Body) + "</pre>"
							}
						} else if mediaType == "text/plain" {
							if bodyType == "text/plain" {
								bodyText += "\n\n" + string(msg.Body)
							} else {
								bodyText += "\n\n<pre>" + string(msg.Body) + "</pre>"
							}
						} else if mediaType == "text/html" {
							if bodyType == "text/plain" {
								bodyType = "text/html"
								bodyText = "<pre>" + bodyText + "</pre>\n\n" + string(msg.Body)
							} else {
								bodyText += "\n\n" + string(msg.Body)
							}
						} else {
							if bodyType != "text/html" {
								bodyType = "text/html"
								bodyText = "<pre>" + bodyText + "</pre>"
							}

							// Put images as HTML tags
							if strings.Index(mediaType, "image/") == 0 {
								bodyText = "\n\n<img src=\"data:" + mediaType + ";base64," + base64.StdEncoding.EncodeToString(msg.Body) + "\"><br>"
							} else {
								bodyText = "\n\n<pre>" + string(msg.Body) + "</pre>"
							}
						}
					}
				}

				return nil
			}

			// Parse the email
			parseBody(email)

			// Trim the body text
			bodyText = strings.TrimSpace(bodyText)

			// Hash the body
			bodyHash := sha256.Sum256([]byte(bodyText))

			// Append body to the parts
			parts = append(parts, &man.Part{
				Hash:        hex.EncodeToString(bodyHash[:]),
				ID:          "body",
				ContentType: bodyType,
				Size:        len(bodyText),
			})

			// Debug info
			log.Debug("Finished parsing the email")

			// Push files into RethinkDB
			for _, file := range files {
				if err := gorethink.Db(config.RethinkDatabase).Table("files").Insert(file).Exec(session); err != nil {
					return describeError(err)
				}
			}

			// Generate the from, to and cc addresses
			from, err := email.Headers.AddressList("from")
			if err != nil {
				from = []*mail.Address{}
			}
			to, err := email.Headers.AddressList("to")
			if err != nil {
				to = []*mail.Address{}
			}
			cc, err := email.Headers.AddressList("cc")
			if err != nil {
				cc = []*mail.Address{}
			}

			// Generate the manifest
			emailID := uniuri.NewLen(uniuri.UUIDLen)
			subject = "Encrypted message (" + emailID + ")"

			s2 := email.Headers.Get("subject")
			if len(s2) > 1 && s2[0] == '=' && s2[1] == '?' {
				s2, _, err = quotedprintable.DecodeHeader(s2)
				if err != nil {
					return describeError(err)
				}
			}

			var fm *mail.Address
			if len(from) > 0 {
				fm = from[0]
			} else {
				fm = &mail.Address{
					Name:    "no from header",
					Address: "invalid",
				}
			}
			rawManifest := &man.Manifest{
				Version: semver.Version{
					Major: 1,
				},
				From:    fm,
				To:      to,
				CC:      cc,
				Subject: s2,
				Parts:   parts,
			}

			// Encrypt the manifest and the body
			encryptedBody, err := shared.EncryptAndArmor([]byte(bodyText), toKeyring)
			if err != nil {
				return describeError(err)
			}
			strManifest, err := man.Write(rawManifest)
			if err != nil {
				return describeError(err)
			}
			encryptedManifest, err := shared.EncryptAndArmor(strManifest, toKeyring)
			if err != nil {
				return describeError(err)
			}

			body = string(encryptedBody)
			manifest = string(encryptedManifest)
			kind = "manifest"

			_ = subject
		} else if kind == "manifest" {
			// Variables used for attachment search
			manifestIndex := -1
			bodyIndex := -1

			// Find indexes of the manifest and the body
			for index, child := range email.Children {
				contentType := child.Headers.Get("Content-Type")

				if strings.Index(contentType, "application/x-pgp-manifest") == 0 {
					manifestIndex = index
				} else if strings.Index(contentType, "multipart/alternative") == 0 {
					bodyIndex = index
				}

				if manifestIndex != -1 && bodyIndex != -1 {
					break
				}
			}

			// Check that we found both parts
			if manifestIndex == -1 || bodyIndex == -1 {
				return describeError(fmt.Errorf("Invalid PGP/Manifest email"))
			}

			// Search for the body child index
			bodyChildIndex := -1
			for index, child := range email.Children[bodyIndex].Children {
				contentType := child.Headers.Get("Content-Type")

				if strings.Index(contentType, "application/pgp-encrypted") == 0 {
					bodyChildIndex = index
					break
				}
			}

			// Check that we found it
			if bodyChildIndex == -1 {
				return describeError(fmt.Errorf("Invalid PGP/Manifest email body"))
			}

			// Find the manifest and the body
			manifest = string(email.Children[manifestIndex].Body)
			body = string(email.Children[bodyIndex].Children[bodyChildIndex].Body)
			subject = "Encrypted email"

			// Gather attachments and insert them into db
			for index, child := range email.Children {
				if index == bodyIndex || index == manifestIndex {
					continue
				}

				_, cdparams, err := mime.ParseMediaType(child.Headers.Get("Content-Disposition"))
				if err != nil {
					return describeError(err)
				}

				for _, account := range accounts {
					fid := uniuri.NewLen(uniuri.UUIDLen)

					if err := gorethink.Db(config.RethinkDatabase).Table("files").Insert(&models.File{
						Resource: models.Resource{
							ID:           fid,
							DateCreated:  time.Now(),
							DateModified: time.Now(),
							Name:         cdparams["filename"],
							Owner:        account.ID,
						},
						Encrypted: models.Encrypted{
							Encoding: "application/pgp-encrypted",
							Data:     string(child.Body),
						},
					}).Exec(session); err != nil {
						return describeError(err)
					}

					if _, ok := fileIDs[account.ID]; !ok {
						fileIDs[account.ID] = []string{}
					}

					fileIDs[account.ID] = append(fileIDs[account.ID], fid)
				}
			}
		} else if kind == "pgpmime" {
			for _, child := range email.Children {
				if strings.Index(child.Headers.Get("Content-Type"), "application/pgp-encrypted") != -1 {
					body = string(child.Body)
					subject = child.Headers.Get("Subject")
					break
				}
			}
		}

		if len(subject) > 1 && subject[0] == '=' && subject[1] == '?' {
			subject, _, err = quotedprintable.DecodeHeader(subject)
			if err != nil {
				return describeError(err)
			}
		}

		// Save the email for each recipient
		for _, account := range accounts {
			// Get 3 user's labels
			cursor, err := gorethink.Db(config.RethinkDatabase).Table("labels").GetAllByIndex("nameOwnerBuiltin", []interface{}{
				"Inbox",
				account.ID,
				true,
			}, []interface{}{
				"Spam",
				account.ID,
				true,
			}, []interface{}{
				"Trash",
				account.ID,
				true,
			}, []interface{}{}).Run(session)
			if err != nil {
				return describeError(err)
			}
			defer cursor.Close()
			var labels []*models.Label
			if err := cursor.All(&labels); err != nil {
				return describeError(err)
			}

			var (
				inbox = labels[0]
				spam  = labels[1]
				trash = labels[2]
			)

			// Get the subject's hash
			subjectHash := email.Headers.Get("Subject-Hash")
			if subjectHash == "" {
				subject := email.Headers.Get("Subject")
				if subject == "" {
					subject = "<no subject>"
				}

				if len(subject) > 1 && subject[0] == '=' && subject[1] == '?' {
					subject, _, err = quotedprintable.DecodeHeader(subject)
					if err != nil {
						return describeError(err)
					}
				}

				subject = shared.StripPrefixes(strings.TrimSpace(subject))

				hash := sha256.Sum256([]byte(subject))
				subjectHash = hex.EncodeToString(hash[:])
			}

			// Generate the email ID
			eid := uniuri.NewLen(uniuri.UUIDLen)

			// Prepare from, to and cc
			from := email.Headers.Get("from")
			if f1, err := email.Headers.AddressList("from"); err == nil && len(f1) > 0 {
				from = strings.TrimSpace(f1[0].Name + " <" + f1[0].Address + ">")
			}
			to := strings.Split(email.Headers.Get("to"), ", ")
			cc := strings.Split(email.Headers.Get("cc"), ", ")
			for i, v := range to {
				to[i] = strings.TrimSpace(v)
			}
			for i, v := range cc {
				cc[i] = strings.TrimSpace(v)
			}

			if len(cc) == 1 && cc[0] == "" {
				cc = nil
			}

			// Transform headers into map[string]string
			fh := map[string]string{}
			for key, values := range email.Headers {
				fh[key] = strings.Join(values, ", ")
			}

			// Find the thread
			var thread *models.Thread

			// First check if either in-reply-to or references headers are set
			irt := ""
			if x := email.Headers.Get("In-Reply-To"); x != "" {
				irt = x
			} else if x := email.Headers.Get("References"); x != "" {
				irt = x
			}

			if irt != "" {
				// Per http://www.jwz.org/doc/threading.html:
				// You can safely assume that the first string between <> in In-Reply-To
				// is the message ID.
				x1i := strings.Index(irt, "<")
				if x1i != -1 {
					x2i := strings.Index(irt[x1i+1:], ">")
					if x2i != -1 {
						irt = irt[x1i+1 : x1i+x2i+1]
					}
				}

				// Look up the parent
				cursor, err := gorethink.Db(config.RethinkDatabase).Table("emails").GetAllByIndex("messageIDOwner", []interface{}{
					irt,
					account.ID,
				}).Run(session)
				if err != nil {
					return describeError(err)
				}
				defer cursor.Close()
				var emails []*models.Email
				if err := cursor.All(&emails); err != nil {
					return describeError(err)
				}

				// Found one = that one is correct
				if len(emails) == 1 {
					cursor, err := gorethink.Db(config.RethinkDatabase).Table("threads").Get(emails[0].Thread).Run(session)
					if err != nil {
						return describeError(err)
					}
					defer cursor.Close()
					if err := cursor.One(&thread); err != nil {
						return describeError(err)
					}
				}
			}

			if thread == nil {
				// Match by subject
				cursor, err := gorethink.Db(config.RethinkDatabase).Table("threads").GetAllByIndex("subjectOwner", []interface{}{
					subjectHash,
					account.ID,
				}).Filter(func(row gorethink.Term) gorethink.Term {
					return row.Field("members").Map(func(member gorethink.Term) gorethink.Term {
						return member.Match(gorethink.Expr(from)).CoerceTo("string").Ne("null")
					}).Contains(gorethink.Expr(true)).And(
						gorethink.Not(
							row.Field("labels").Contains(spam.ID).Or(
								row.Field("labels").Contains(trash.ID),
							),
						),
					)
				}).Run(session)
				if err != nil {
					return describeError(err)
				}
				defer cursor.Close()
				var threads []*models.Thread
				if err := cursor.All(&threads); err != nil {
					return describeError(err)
				}

				if len(threads) > 0 {
					thread = threads[0]
				}
			}

			if thread == nil {
				secure := "all"
				if initialKind == "raw" {
					secure = "none"
				}

				labels := []string{inbox.ID}
				if isSpam {
					labels = append(labels, spam.ID)
				}

				thread = &models.Thread{
					Resource: models.Resource{
						ID:           uniuri.NewLen(uniuri.UUIDLen),
						DateCreated:  time.Now(),
						DateModified: time.Now(),
						Name:         "Encrypted thread",
						Owner:        account.ID,
					},
					Emails:      []string{eid},
					Labels:      labels,
					Members:     append(append(to, cc...), from),
					IsRead:      false,
					SubjectHash: subjectHash,
					Secure:      secure,
				}

				if err := gorethink.Db(config.RethinkDatabase).Table("threads").Insert(thread).Exec(session); err != nil {
					return describeError(err)
				}
			} else {
				var desiredID string
				if isSpam {
					desiredID = spam.ID
				} else {
					desiredID = inbox.ID
				}

				foundLabel := false
				for _, label := range thread.Labels {
					if label == desiredID {
						foundLabel = true
						break
					}
				}
				if !foundLabel {
					thread.Labels = append(thread.Labels, desiredID)
				}

				thread.Emails = append(thread.Emails, eid)

				update := map[string]interface{}{
					"date_modified": gorethink.Now(),
					"is_read":       false,
					"labels":        thread.Labels,
					"emails":        thread.Emails,
				}

				// update thread.secure depending on email's kind
				if (initialKind == "raw" && thread.Secure == "all") ||
					(initialKind == "manifest" && thread.Secure == "none") ||
					(initialKind == "pgpmime" && thread.Secure == "none") {
					update["secure"] = "some"
				}

				if err := gorethink.Db(config.RethinkDatabase).Table("threads").Get(thread.ID).Update(update).Exec(session); err != nil {
					return describeError(err)
				}
			}

			// Generate list of all owned emails
			ownEmails := map[string]struct{}{}
			for domain, _ := range domains {
				ownEmails[account.Name+"@"+domain] = struct{}{}
			}

			// Remove ownEmails from to and cc
			to2 := []string{}
			for _, value := range to {
				addr, err := mail.ParseAddress(value)
				if err != nil {
					// Mail is probably empty
					continue
				}

				if _, ok := ownEmails[addr.Address]; !ok {
					to2 = append(to2, value)
				}
			}

			to = to2

			if cc != nil {
				cc2 := []string{}
				for _, value := range cc {
					addr, err := mail.ParseAddress(value)
					if err != nil {
						continue
					}

					if _, ok := ownEmails[addr.Address]; !ok {
						cc2 = append(cc2, value)
					}
				}

				cc = cc2
			}

			// Prepare a new email
			es := &models.Email{
				Resource: models.Resource{
					ID:           eid,
					DateCreated:  time.Now(),
					DateModified: time.Now(),
					Name:         subject,
					Owner:        account.ID,
				},
				Kind:      kind,
				From:      from,
				To:        to,
				CC:        cc,
				Body:      body,
				Thread:    thread.ID,
				MessageID: strings.Trim(email.Headers.Get("Message-ID"), "<>"), // todo: create a message id parser
				Status:    "received",
			}

			if fileIDs != nil {
				es.Files = fileIDs[account.ID]
			}

			if manifest != "" {
				es.Manifest = manifest
			}

			// Insert the email
			if err := gorethink.Db(config.RethinkDatabase).Table("emails").Insert(es).Exec(session); err != nil {
				return describeError(err)
			}

			// Prepare a notification message
			notification, err := json.Marshal(map[string]interface{}{
				"id":    eid,
				"owner": account.ID,
			})
			if err != nil {
				return describeError(err)
			}

			// Notify the cluster
			if err := producer.Publish("email_receipt", notification); err != nil {
				return describeError(err)
			}

			// Trigger the hooks
			hook, err := json.Marshal(&events.Incoming{
				Email:   eid,
				Account: account.ID,
			})
			if err != nil {
				return describeError(err)
			}

			// Push it to nsq
			if err = producer.Publish("hook_incoming", hook); err != nil {
				return describeError(err)
			}

			log.WithFields(logrus.Fields{
				"id": eid,
			}).Info("Finished processing an email")
		}

		return nil
	}
}
Example #10
0
func create(w http.ResponseWriter, req *http.Request) {
	// Decode the body
	var msg createInput
	err := json.NewDecoder(req.Body).Decode(&msg)
	if err != nil {
		writeJSON(w, errorMsg{
			Success: false,
			Message: err.Error(),
		})
		return
	}

	// Fetch the invite from database
	cursor, err := r.Db(*rethinkName).Table("invites").Get(msg.Token).Run(session)
	if err != nil {
		writeJSON(w, errorMsg{
			Success: false,
			Message: err.Error(),
		})
		return
	}
	var invite *Invite
	err = cursor.One(&invite)
	if err != nil {
		writeJSON(w, errorMsg{
			Success: false,
			Message: err.Error(),
		})
		return
	}

	// Normalize the username
	styledName := msg.Username
	msg.Username = utils.RemoveDots(utils.NormalizeUsername(msg.Username))

	var account *models.Account

	// If there's no account id, then simply check args
	if invite.AccountID == "" {
		if !govalidator.IsEmail(msg.Email) {
			writeJSON(w, errorMsg{
				Success: false,
				Message: "Invalid email address",
			})
			return
		}

		// Check if address is taken
		cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session)
		if err == nil || cursor != nil {
			writeJSON(w, freeMsg{
				Success:       false,
				UsernameTaken: true,
			})
			return
		}

		// Check if email is used
		cursor, err = r.Db(*rethinkAPIName).Table("accounts").
			GetAllByIndex("alt_email", msg.Email).
			Filter(r.Row.Field("id").Ne(r.Expr(invite.AccountID))).
			Count().Run(session)
		if err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}
		var emailCount int
		err = cursor.One(&emailCount)
		if err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}
		if emailCount > 0 {
			writeJSON(w, freeMsg{
				Success:   false,
				EmailUsed: true,
			})
			return
		}

		// Prepare a new account
		account = &models.Account{
			Resource:   models.MakeResource("", msg.Username),
			AltEmail:   msg.Email,
			StyledName: styledName,
			Status:     "registered",
			Type:       "supporter",
		}

		// Update the invite
		invite.AccountID = account.ID
		err = r.Db(*rethinkName).Table("invites").Get(invite.ID).Update(map[string]interface{}{
			"account_id": invite.AccountID,
		}).Exec(session)
		if err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}

		// Insert the account into db
		err = r.Db(*rethinkAPIName).Table("accounts").Insert(account).Exec(session)
		if err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}
	} else {
		cursor, err = r.Db(*rethinkAPIName).Table("accounts").Get(invite.AccountID).Run(session)
		if err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}
		defer cursor.Close()
		if err := cursor.One(&account); err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}

		if account.Name != "" && account.Name != msg.Username {
			writeJSON(w, errorMsg{
				Success: false,
				Message: "Invalid username",
			})
			return
		} else if account.Name == "" {
			// Check if address is taken
			cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session)
			if err == nil || cursor != nil {
				writeJSON(w, errorMsg{
					Success: false,
					Message: "Username is taken",
				})
				return
			}
		}

		if account.AltEmail != "" && account.AltEmail != msg.Email {
			writeJSON(w, errorMsg{
				Success: false,
				Message: "Invalid email",
			})
			return
		}

		if account.AltEmail == "" {
			if !govalidator.IsEmail(msg.Email) {
				writeJSON(w, errorMsg{
					Success: false,
					Message: "Invalid email address",
				})
				return
			}

			// Check if email is used
			cursor, err = r.Db(*rethinkAPIName).Table("accounts").
				GetAllByIndex("alt_email", msg.Email).
				Filter(r.Row.Field("id").Ne(r.Expr(invite.AccountID))).
				Count().Run(session)
			if err != nil {
				writeJSON(w, errorMsg{
					Success: false,
					Message: err.Error(),
				})
				return
			}
			defer cursor.Close()

			var emailCount int
			err = cursor.One(&emailCount)
			if err != nil {
				writeJSON(w, errorMsg{
					Success: false,
					Message: err.Error(),
				})
				return
			}
			if emailCount > 0 {
				writeJSON(w, errorMsg{
					Success: false,
					Message: "Email is already used",
				})
				return
			}
		}

		if err := r.Db(*rethinkAPIName).Table("accounts").Get(invite.AccountID).Update(map[string]interface{}{
			"type": "supporter",
		}).Exec(session); err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}
	}

	// Generate a new invite token for the user
	token := &models.Token{
		Resource: models.MakeResource(account.ID, "Invitation token from invite-api"),
		Type:     "verify",
		Expiring: models.Expiring{
			ExpiryDate: time.Now().UTC().Add(time.Hour * 12),
		},
	}

	// Insert it into db
	err = r.Db(*rethinkAPIName).Table("tokens").Insert(token).Exec(session)
	if err != nil {
		writeJSON(w, errorMsg{
			Success: false,
			Message: err.Error(),
		})
		return
	}

	// Here be dragons. Thou art forewarned.
	/*go func() {
		// Watch the changes
		cursor, err := r.Db(*rethinkAPIName).Table("accounts").Get(account.ID).Changes().Run(session)
		if err != nil {
			log.Print("Error while watching changes of user " + account.Name + " - " + err.Error())
			return
		}
		defer cursor.Close()

		// Generate a timeout "flag"
		ts := uniuri.New()

		// Read them
		c := make(chan struct{})
		go func() {
			var change struct {
				NewValue map[string]interface{} `gorethink:"new_val"`
			}
			for cursor.Next(&change) {
				if status, ok := change.NewValue["status"]; ok {
					if x, ok := status.(string); ok && x == "setup" {
						c <- struct{}{}
						return
					}
				}

				if iat, ok := change.NewValue["_invite_api_timeout"]; ok {
					if x, ok := iat.(string); ok && x == ts {
						log.Print("Account setup watcher timeout for name " + account.Name)
						return
					}
				}
			}
		}()

		// Block the goroutine
		select {
		case <-c:
			if err := r.Db(*rethinkName).Table("invites").Get(invite.ID).Delete().Exec(session); err != nil {
				log.Print("Unable to delete an invite. " + invite.ID + " - " + account.ID)
				return
			}
			return
		case <-time.After(12 * time.Hour):
			if err := r.Db(*rethinkAPIName).Table("accounts").Get(account.ID).Update(map[string]interface{}{
				"_invite_api_timeout": ts,
			}).Exec(session); err != nil {
				log.Print("Failed to make a goroutine timeout. " + account.ID)
			}
			return
		}
	}()*/

	// jk f**k that
	if err := r.Db(*rethinkName).Table("invites").Get(invite.ID).Delete().Exec(session); err != nil {
		log.Print("Unable to delete an invite. " + invite.ID + " - " + account.ID)
		return
	}

	// Return the token
	writeJSON(w, createMsg{
		Success: true,
		Code:    token.ID,
	})
}
Example #11
0
func StartQueue(config *shared.Flags) {
	// Initialize a new logger
	log := logrus.New()
	if config.LogFormatterType == "text" {
		log.Formatter = &logrus.TextFormatter{
			ForceColors: config.ForceColors,
		}
	} else if config.LogFormatterType == "json" {
		log.Formatter = &logrus.JSONFormatter{}
	}

	log.Level = logrus.DebugLevel

	// Create a new header encoder
	he := quotedprintable.Q.NewHeaderEncoder("utf-8")

	// Initialize the database connection
	session, err := gorethink.Connect(gorethink.ConnectOpts{
		Address: config.RethinkAddress,
		AuthKey: config.RethinkKey,
		MaxIdle: 10,
		Timeout: time.Second * 10,
	})
	if err != nil {
		log.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("Unable to connect to RethinkDB")
	}

	// Create a new producer
	consumer, err := nsq.NewConsumer("send_email", "receive", nsq.NewConfig())
	if err != nil {
		log.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("Unable to create a consumer")
	}

	// Connect to NSQ
	producer, err := nsq.NewProducer(config.NSQDAddress, nsq.NewConfig())
	if err != nil {
		log.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("Unable to connect to NSQd")
	}

	// Load a DKIM signer
	var dkimSigner map[string]*dkim.DKIM
	if config.DKIMKey != "" {
		dkimSigner = map[string]*dkim.DKIM{}

		key, err := ioutil.ReadFile(config.DKIMKey)
		if err != nil {
			log.WithFields(logrus.Fields{
				"error": err.Error(),
			}).Fatal("Unable to read DKIM private key")
		}

		for domain, _ := range domains {
			dkimConf, err := dkim.NewConf(domain, config.DKIMSelector)
			if err != nil {
				log.WithFields(logrus.Fields{
					"error": err.Error(),
				}).Fatal("Unable to create a new DKIM conf object")
			}

			dk, err := dkim.New(dkimConf, key)
			if err != nil {
				log.WithFields(logrus.Fields{
					"error": err.Error(),
				}).Fatal("Unable to create a new DKIM signer")
			}

			dkimSigner[domain] = dk
		}
	}

	consumer.AddConcurrentHandlers(nsq.HandlerFunc(func(msg *nsq.Message) error {
		var id string
		if err := json.Unmarshal(msg.Body, &id); err != nil {
			return err
		}

		// Get the email from the database
		cursor, err := gorethink.Db(config.RethinkDatabase).Table("emails").Get(id).Run(session)
		if err != nil {
			return err
		}
		defer cursor.Close()
		var email *models.Email
		if err := cursor.One(&email); err != nil {
			return err
		}

		// Get the thread
		cursor, err = gorethink.Db(config.RethinkDatabase).Table("threads").Get(email.Thread).Run(session)
		if err != nil {
			return err
		}
		defer cursor.Close()
		var thread *models.Thread
		if err := cursor.One(&thread); err != nil {
			return err
		}

		// Get the proper In-Reply-To
		hasInReplyTo := false
		inReplyTo := ""

		// Fetch received emails in the thread
		cursor, err = gorethink.Db(config.RethinkDatabase).Table("emails").GetAllByIndex("threadStatus", []interface{}{
			thread.ID,
			"received",
		}).Pluck("date_created", "message_id", "from").OrderBy(gorethink.Desc(gorethink.Row.Field("date_created"))).
			Filter(func(row gorethink.Term) gorethink.Term {
				return gorethink.Expr(email.To).Contains(row.Field("from"))
			}).Limit(1).Run(session)
		if err != nil {
			return err
		}
		defer cursor.Close()
		var emid []*models.Email
		if err := cursor.All(&emid); err != nil {
			return err
		}

		if len(emid) == 1 {
			hasInReplyTo = true
			inReplyTo = emid[0].MessageID
		}

		// Fetch the files
		var files []*models.File
		if email.Files != nil && len(email.Files) > 0 {
			filesList := []interface{}{}
			for _, v := range email.Files {
				filesList = append(filesList, v)
			}
			cursor, err = gorethink.Db(config.RethinkDatabase).Table("files").GetAll(filesList...).Run(session)
			if err != nil {
				return err
			}
			defer cursor.Close()
			if err := cursor.All(&files); err != nil {
				return err
			}
		} else {
			files = []*models.File{}
		}

		// Fetch the owner
		cursor, err = gorethink.Db(config.RethinkDatabase).Table("accounts").Get(email.Owner).Run(session)
		if err != nil {
			return err
		}
		defer cursor.Close()
		var account *models.Account
		if err := cursor.One(&account); err != nil {
			return err
		}

		// Declare a contents variable
		contents := ""

		ctxFrom := email.From

		// Check if charset is set
		if !strings.Contains(email.ContentType, "; charset=") {
			email.ContentType += "; charset=utf-8"
		}

		if email.Kind == "raw" {
			// Encode the email
			if files == nil || len(files) == 0 {
				buffer := &bytes.Buffer{}

				context := &rawSingleContext{
					From:         ctxFrom,
					CombinedTo:   strings.Join(email.To, ", "),
					MessageID:    email.MessageID,
					HasInReplyTo: hasInReplyTo,
					InReplyTo:    inReplyTo,
					Subject:      he.Encode(email.Name),
					ContentType:  email.ContentType,
					Body:         quotedprintable.EncodeToString([]byte(email.Body)),
					Date:         email.DateCreated.Format(time.RubyDate),
				}

				if email.CC != nil && len(email.CC) > 0 {
					context.HasCC = true
					context.CombinedCC = strings.Join(email.CC, ", ")
				}

				if email.ReplyTo != "" {
					context.HasReplyTo = true
					context.ReplyTo = email.ReplyTo
				}

				if err := rawSingleTemplate.Execute(buffer, context); err != nil {
					return err
				}

				contents = buffer.String()
			} else {
				buffer := &bytes.Buffer{}

				emailFiles := []*emailFile{}
				for _, file := range files {
					emailFiles = append(emailFiles, &emailFile{
						Encoding: file.Encoding,
						Name:     file.Name,
						Body:     base64.StdEncoding.EncodeToString([]byte(file.Data)),
					})
				}

				context := &rawMultiContext{
					From:         ctxFrom,
					CombinedTo:   strings.Join(email.To, ", "),
					MessageID:    email.MessageID,
					HasInReplyTo: hasInReplyTo,
					InReplyTo:    inReplyTo,
					Boundary1:    uniuri.NewLen(20),
					Subject:      he.Encode(email.Name),
					ContentType:  email.ContentType,
					Body:         quotedprintable.EncodeToString([]byte(email.Body)),
					Files:        emailFiles,
					Date:         email.DateCreated.Format(time.RubyDate),
				}

				if email.CC != nil && len(email.CC) > 0 {
					context.HasCC = true
					context.CombinedCC = strings.Join(email.CC, ", ")
				}

				if email.ReplyTo != "" {
					context.HasReplyTo = true
					context.ReplyTo = email.ReplyTo
				}

				if err := rawMultiTemplate.Execute(buffer, context); err != nil {
					return err
				}

				contents = buffer.String()
			}

			// Fetch owner's account
			cursor, err = gorethink.Db(config.RethinkDatabase).Table("accounts").Get(email.Owner).Run(session)
			if err != nil {
				return err
			}
			defer cursor.Close()
			var account *models.Account
			if err := cursor.One(&account); err != nil {
				return err
			}

			// Get owner's key
			var key *models.Key
			if account.PublicKey != "" {
				cursor, err = gorethink.Db(config.RethinkDatabase).Table("keys").Get(account.PublicKey).Run(session)
				if err != nil {
					return err
				}
				defer cursor.Close()
				if err := cursor.One(&key); err != nil {
					return err
				}
			} else {
				cursor, err = gorethink.Db(config.RethinkDatabase).Table("keys").GetAllByIndex("owner", account.ID).Run(session)
				if err != nil {
					return err
				}
				defer cursor.Close()
				var keys []*models.Key
				if err := cursor.All(&keys); err != nil {
					return err
				}

				key = keys[0]
			}

			// Parse the key
			keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key.Key))
			if err != nil {
				return err
			}

			// From, to and cc parsing
			fromAddr, err := mail.ParseAddress(email.From)
			if err != nil {
				fromAddr = &mail.Address{
					Address: email.From,
				}
			}
			toAddr, err := mail.ParseAddressList(strings.Join(email.To, ", "))
			if err != nil {
				toAddr = []*mail.Address{}
				for _, addr := range email.To {
					toAddr = append(toAddr, &mail.Address{
						Address: addr,
					})
				}
			}

			// Prepare a new manifest
			manifest := &man.Manifest{
				Version: semver.Version{
					Major: 1,
				},
				From:    fromAddr,
				To:      toAddr,
				Subject: he.Encode(email.Name),
				Parts:   []*man.Part{},
			}

			if email.CC != nil && len(email.CC) > 0 {
				ccAddr, nil := mail.ParseAddressList(strings.Join(email.CC, ", "))
				if err != nil {
					ccAddr = []*mail.Address{}
					for _, addr := range email.CC {
						ccAddr = append(ccAddr, &mail.Address{
							Address: addr,
						})
					}
				}

				manifest.CC = ccAddr
			}

			// Encrypt and hash the body
			encryptedBody, err := shared.EncryptAndArmor([]byte(email.Body), keyring)
			if err != nil {
				return err
			}
			hash := sha256.Sum256([]byte(email.Body))

			// Append body to the parts
			manifest.Parts = append(manifest.Parts, &man.Part{
				ID:          "body",
				Hash:        hex.EncodeToString(hash[:]),
				ContentType: email.ContentType,
				Size:        len(email.Body),
			})

			// Encrypt the attachments
			for _, file := range files {
				// Encrypt the attachment
				cipher, err := shared.EncryptAndArmor([]byte(file.Data), keyring)
				if err != nil {
					return err
				}

				// Hash it
				hash := sha256.Sum256([]byte(file.Data))

				// Generate a random ID
				id := uniuri.NewLen(20)

				// Push the attachment into the manifest
				manifest.Parts = append(manifest.Parts, &man.Part{
					ID:          id,
					Hash:        hex.EncodeToString(hash[:]),
					Filename:    file.Name,
					ContentType: file.Encoding,
					Size:        len(file.Data),
				})

				// Replace the file in database
				err = gorethink.Db(config.RethinkDatabase).Table("files").Get(file.ID).Replace(&models.File{
					Resource: models.Resource{
						ID:           file.ID,
						DateCreated:  file.DateCreated,
						DateModified: time.Now(),
						Name:         id + ".pgp",
						Owner:        account.ID,
					},
					Encrypted: models.Encrypted{
						Encoding: "application/pgp-encrypted",
						Data:     string(cipher),
					},
				}).Exec(session)
				if err != nil {
					return err
				}
			}

			// Encrypt the manifest
			strManifest, err := man.Write(manifest)
			if err != nil {
				return err
			}
			encryptedManifest, err := shared.EncryptAndArmor(strManifest, keyring)
			if err != nil {
				return err
			}

			err = gorethink.Db(config.RethinkDatabase).Table("emails").Get(email.ID).Replace(&models.Email{
				Resource: models.Resource{
					ID:           email.ID,
					DateCreated:  email.DateCreated,
					DateModified: time.Now(),
					Name:         "Encrypted message (" + email.ID + ")",
					Owner:        account.ID,
				},
				Kind:      "manifest",
				From:      email.From,
				To:        email.To,
				CC:        email.CC,
				BCC:       email.BCC,
				Files:     email.Files,
				Manifest:  string(encryptedManifest),
				Body:      string(encryptedBody),
				Thread:    email.Thread,
				MessageID: email.MessageID,
			}).Exec(session)
			if err != nil {
				return err
			}
		} else if email.Kind == "pgpmime" {
			buffer := &bytes.Buffer{}

			context := &pgpContext{
				From:         ctxFrom,
				CombinedTo:   strings.Join(email.To, ", "),
				MessageID:    email.MessageID,
				HasInReplyTo: hasInReplyTo,
				InReplyTo:    inReplyTo,
				Subject:      email.Name,
				ContentType:  email.ContentType,
				Body:         email.Body,
				Date:         email.DateCreated.Format(time.RubyDate),
			}

			if email.CC != nil && len(email.CC) > 0 {
				context.HasCC = true
				context.CombinedCC = strings.Join(email.CC, ", ")
			}

			if email.ReplyTo != "" {
				context.HasReplyTo = true
				context.ReplyTo = email.ReplyTo
			}

			if err := pgpTemplate.Execute(buffer, context); err != nil {
				return err
			}

			contents = buffer.String()
		} else if email.Kind == "manifest" {
			if files == nil || len(files) == 0 {
				buffer := &bytes.Buffer{}

				context := &manifestSingleContext{
					From:         ctxFrom,
					CombinedTo:   strings.Join(email.To, ", "),
					MessageID:    email.MessageID,
					HasInReplyTo: hasInReplyTo,
					InReplyTo:    inReplyTo,
					Subject:      he.Encode(email.Name),
					Boundary1:    uniuri.NewLen(20),
					Boundary2:    uniuri.NewLen(20),
					ID:           email.ID,
					Body:         email.Body,
					Manifest:     email.Manifest,
					SubjectHash:  thread.SubjectHash,
					Date:         email.DateCreated.Format(time.RubyDate),
				}

				if email.CC != nil && len(email.CC) > 0 {
					context.HasCC = true
					context.CombinedCC = strings.Join(email.CC, ", ")
				}

				if email.ReplyTo != "" {
					context.HasReplyTo = true
					context.ReplyTo = email.ReplyTo
				}

				if err := manifestSingleTemplate.Execute(buffer, context); err != nil {
					return err
				}

				contents = buffer.String()
			} else {
				buffer := &bytes.Buffer{}

				emailFiles := []*emailFile{}
				for _, file := range files {
					emailFiles = append(emailFiles, &emailFile{
						Encoding: file.Encoding,
						Name:     file.Name,
						Body:     file.Data,
					})
				}

				context := &manifestMultiContext{
					From:         ctxFrom,
					CombinedTo:   strings.Join(email.To, ", "),
					MessageID:    email.MessageID,
					HasInReplyTo: hasInReplyTo,
					InReplyTo:    inReplyTo,
					Subject:      he.Encode(email.Name),
					Boundary1:    uniuri.NewLen(20),
					Boundary2:    uniuri.NewLen(20),
					ID:           email.ID,
					Body:         email.Body,
					Manifest:     email.Manifest,
					SubjectHash:  thread.SubjectHash,
					Files:        emailFiles,
					Date:         email.DateCreated.Format(time.RubyDate),
				}

				if email.CC != nil && len(email.CC) > 0 {
					context.HasCC = true
					context.CombinedCC = strings.Join(email.CC, ", ")
				}

				if email.ReplyTo != "" {
					context.HasReplyTo = true
					context.ReplyTo = email.ReplyTo
				}

				if err := manifestMultiTemplate.Execute(buffer, context); err != nil {
					return err
				}

				contents = buffer.String()
			}
		}

		recipients := email.To
		if email.CC != nil {
			recipients = append(recipients, email.CC...)
		}

		nsqmsg, _ := json.Marshal(map[string]interface{}{
			"id":    email.ID,
			"owner": email.Owner,
		})

		// Sign the email
		if dkimSigner != nil {
			parts := strings.Split(email.From, "@")
			if len(parts) == 2 {
				if _, ok := dkimSigner[parts[1]]; ok {
					// Replace newlines with \r\n
					contents = strings.Replace(contents, "\n", "\r\n", -1)

					// Sign it
					data, err := dkimSigner[parts[1]].Sign([]byte(contents))
					if err != nil {
						log.Print(err)
						return err
					}

					// Replace contents with signed
					contents = strings.Replace(string(data), "\r\n", "\n", -1)
				}
			}
		}

		if err := smtp.SendMail(config.SMTPAddress, nil, email.From, recipients, []byte(contents)); err != nil {
			err := producer.Publish("email_bounced", nsqmsg)
			if err != nil {
				log.WithFields(logrus.Fields{
					"error": err,
				}).Error("Unable to publish a bounce msg")
			}
		} else {
			err := producer.Publish("email_delivery", nsqmsg)
			if err != nil {
				log.WithFields(logrus.Fields{
					"error": err,
				}).Error("Unable to publish a bounce msg")
			}
		}
		err = gorethink.Db(config.RethinkDatabase).Table("emails").Get(email.ID).Update(map[string]interface{}{
			"status": "sent",
		}).Exec(session)
		if err != nil {
			log.WithFields(logrus.Fields{
				"error": err,
			}).Error("Unable to mark an email as sent")
		}

		msg.Finish()

		return nil
	}), 10)

	if err := consumer.ConnectToNSQLookupd(config.LookupdAddress); err != nil {
		log.WithFields(logrus.Fields{
			"error": err,
		}).Fatal("Unable to connect to nsqlookupd")
	}

	log.Info("Connected to NSQ and awaiting data")
}
Example #12
0
func (e *EmailsTable) List(
	owner string,
	sort []string,
	offset int,
	limit int,
	thread string,
) ([]*models.Email, error) {

	filter := map[string]interface{}{}

	if owner != "" {
		filter["owner"] = owner
	}

	if thread != "" {
		filter["thread"] = thread
	}

	term := e.GetTable().Filter(filter).Filter(gorethink.Not(gorethink.Row.Field("status").Eq(gorethink.Expr("queued"))))

	// If sort array has contents, parse them and add to the term
	if sort != nil && len(sort) > 0 {
		var conds []interface{}
		for _, cond := range sort {
			if cond[0] == '-' {
				conds = append(conds, gorethink.Desc(cond[1:]))
			} else if cond[0] == '+' || cond[0] == ' ' {
				conds = append(conds, gorethink.Asc(cond[1:]))
			} else {
				conds = append(conds, gorethink.Asc(cond))
			}
		}

		term = term.OrderBy(conds...)
	}

	// Slice the result in 3 cases
	if offset != 0 && limit == 0 {
		term = term.Skip(offset)
	}

	if offset == 0 && limit != 0 {
		term = term.Limit(limit)
	}

	if offset != 0 && limit != 0 {
		term = term.Slice(offset, offset+limit)
	}

	// Run the query
	cursor, err := term.Run(e.GetSession())
	if err != nil {
		return nil, err
	}
	defer cursor.Close()

	// Fetch the cursor
	var resp []*models.Email
	err = cursor.All(&resp)
	if err != nil {
		return nil, err
	}

	return resp, nil
}
Example #13
0
func enrichFilter(input map[string]interface{}) {
	for k, v1 := range input {
		if v2, ok := v1.(string); ok {
			if strings.HasPrefix(v2, "not~") {
				if len(v2) > 4 {
					input[k] = gorethink.Not(gorethink.Expr(v2[5:]))
				}
			} else if strings.HasPrefix(v2, "lt~") && len(v2) > 3 {
				if v2[3:] == "now" {
					input[k] = gorethink.Lt(gorethink.Now())
				} else {
					t, err := time.Parse(time.RFC3339, v2[3:])
					if err == nil {
						input[k] = gorethink.Lt(gorethink.Expr(t))
					} else {
						i, err := strconv.Atoi(v2[3:])
						if err == nil {
							input[k] = gorethink.Lt(gorethink.Expr(i))
						}
					}
				}
			} else if strings.HasPrefix(v2, "le~") && len(v2) > 3 {
				if v2[3:] == "now" {
					input[k] = gorethink.Le(gorethink.Now())
				} else {
					t, err := time.Parse(time.RFC3339, v2[3:])
					if err == nil {
						input[k] = gorethink.Le(gorethink.Expr(t))
					} else {
						i, err := strconv.Atoi(v2[3:])
						if err == nil {
							input[k] = gorethink.Le(gorethink.Expr(i))
						}
					}
				}
			} else if strings.HasPrefix(v2, "gt~") && len(v2) > 3 {
				if v2[3:] == "now" {
					input[k] = gorethink.Gt(gorethink.Now())
				} else {
					t, err := time.Parse(time.RFC3339, v2[3:])
					if err == nil {
						input[k] = gorethink.Gt(gorethink.Expr(t))
					} else {
						i, err := strconv.Atoi(v2[3:])
						if err == nil {
							input[k] = gorethink.Gt(gorethink.Expr(i))
						}
					}
				}
			} else if strings.HasPrefix(v2, "ge~") && len(v2) > 3 {
				if v2[3:] == "now" {
					input[k] = gorethink.Ge(gorethink.Now())
				} else {
					t, err := time.Parse(time.RFC3339, v2[3:])
					if err == nil {
						input[k] = gorethink.Ge(gorethink.Expr(t))
					} else {
						i, err := strconv.Atoi(v2[3:])
						if err == nil {
							input[k] = gorethink.Ge(gorethink.Expr(i))
						}
					}
				}
			}
		}
	}
}
Example #14
0
func free(w http.ResponseWriter, req *http.Request) {
	// Decode the POST body
	var msg freeInput
	err := json.NewDecoder(req.Body).Decode(&msg)
	if err != nil {
		writeJSON(w, errorMsg{
			Success: false,
			Message: err.Error(),
		})
		return
	}

	// Fetch the invite from database
	cursor, err := r.Db(*rethinkName).Table("invites").Get(msg.Token).Run(session)
	if err != nil {
		writeJSON(w, errorMsg{
			Success: false,
			Message: err.Error(),
		})
		return
	}
	var invite *Invite
	err = cursor.One(&invite)
	if err != nil {
		writeJSON(w, errorMsg{
			Success: false,
			Message: err.Error(),
		})
		return
	}

	// Normalize the username - make it lowercase and remove dots
	msg.Username = utils.RemoveDots(utils.NormalizeUsername(msg.Username))

	if invite.AccountID != "" {
		// Fetch account from database
		cursor, err := r.Db(*rethinkAPIName).Table("accounts").Get(invite.AccountID).Run(session)
		if err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}
		var account *models.Account
		if err := cursor.One(&account); err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}
		if account.Name != "" && account.Name != msg.Username {
			writeJSON(w, errorMsg{
				Success: false,
				Message: "Invalid username",
			})
			return
		}

		if account.AltEmail != "" && account.AltEmail != msg.Email {
			writeJSON(w, errorMsg{
				Success: false,
				Message: "Invalid email",
			})
			return
		}

		if account.AltEmail == "" && !govalidator.IsEmail(msg.Email) {
			writeJSON(w, errorMsg{
				Success: false,
				Message: "Invalid email address",
			})
			return
		}

		if account.Name == "" {
			// Check if address is taken
			cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session)
			if err == nil || cursor != nil {
				writeJSON(w, freeMsg{
					Success:       false,
					UsernameTaken: true,
				})
				return
			}
		}

		if account.AltEmail == "" {
			// Check if email is used
			cursor, err = r.Db(*rethinkAPIName).Table("accounts").
				GetAllByIndex("alt_email", msg.Email).Count().Run(session)
			if err != nil {
				writeJSON(w, errorMsg{
					Success: false,
					Message: err.Error(),
				})
				return
			}
			var emailCount int
			err = cursor.One(&emailCount)
			if err != nil {
				writeJSON(w, errorMsg{
					Success: false,
					Message: err.Error(),
				})
				return
			}
			if emailCount > 0 {
				writeJSON(w, freeMsg{
					Success:   false,
					EmailUsed: true,
				})
				return
			}
		}

		// Return the result
		writeJSON(w, freeMsg{
			Success: true,
		})
	} else {
		if !govalidator.IsEmail(msg.Email) {
			writeJSON(w, errorMsg{
				Success: false,
				Message: "Invalid email address",
			})
			return
		}

		// Check if address is taken
		cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session)
		if err == nil || cursor != nil {
			writeJSON(w, freeMsg{
				Success:       false,
				UsernameTaken: true,
			})
			return
		}

		// Check if email is used
		cursor, err = r.Db(*rethinkAPIName).Table("accounts").
			GetAllByIndex("alt_email", msg.Email).
			Filter(r.Row.Field("id").Ne(r.Expr(invite.AccountID))).
			Count().Run(session)
		if err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}
		var emailCount int
		err = cursor.One(&emailCount)
		if err != nil {
			writeJSON(w, errorMsg{
				Success: false,
				Message: err.Error(),
			})
			return
		}
		if emailCount > 0 {
			writeJSON(w, freeMsg{
				Success:   false,
				EmailUsed: true,
			})
			return
		}

		// Return the result
		writeJSON(w, freeMsg{
			Success: true,
		})
	}
}