예제 #1
0
func (server TwilioSMSCallbackServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	periwinkle.Logf("TwilioCallback")
	fmt.Fprintf(w, "Hi there, I love %s!", req.URL.String())

	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		periwinkle.LogErr(locale.UntranslatedError(err))
		return
	}

	values, err := url.ParseQuery(string(body))
	if err != nil {
		periwinkle.LogErr(locale.UntranslatedError(err))
		return
	}

	status := backend.TwilioSMSMessage{
		MessageStatus: values.Get("MessageStatus"),
		ErrorCode:     values.Get("ErrorCode"),
		MessageSID:    values.Get("MessageSid"),
	}
	server.DB.Do(func(db *periwinkle.Tx) {
		status.Save(db)
	})
}
예제 #2
0
파일: main.go 프로젝트: LukeShu/periwinkle
func checkNumber(config *periwinkle.Cfg, tx *periwinkle.Tx, number backend.TwilioNumber) {
	url := "https://api.twilio.com/2010-04-01/Accounts/" + config.TwilioAccountID + "/Messages.json?To=" + number.Number
	if lastPoll != timeZero {
		url += "&DateSent>=" + strings.Split(lastPoll.UTC().String(), " ")[0]
	}

	req, _ := http.NewRequest("GET", url, nil)
	req.SetBasicAuth(config.TwilioAccountID, config.TwilioAuthToken)
	resp, uerr := (&http.Client{}).Do(req)
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
	}

	defer resp.Body.Close()
	body, uerr := ioutil.ReadAll(resp.Body)
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
	}

	// converts JSON messages
	var page twilio.Paging
	json.Unmarshal([]byte(body), &page)

	for _, message := range page.Messages {
		timeSend, uerr := time.Parse(time.RFC1123Z, message.DateSent)
		if uerr != nil {
			periwinkle.LogErr(locale.UntranslatedError(uerr))
			continue
		}
		if timeSend.Unix() < lastPoll.Unix() {
			periwinkle.Logf("message %q older than our last poll; ignoring", message.Sid)
			continue
		}
		user := backend.GetUserByAddress(tx, "sms", message.From)
		if user == nil {
			periwinkle.Logf("could not figure out which user has number %q", message.From)
			continue
		}
		group := backend.GetGroupByUserAndTwilioNumber(tx, user.ID, message.To)
		if group == nil {
			periwinkle.Logf("could not figure out which group this is meant for: user: %q, number: %q", user.ID, message.To)
			continue
		}
		periwinkle.Logf("received message for group %q", group.ID)
		MessageBuilder{
			Maildir: config.Mailstore,
			Headers: map[string]string{
				"To":      group.ID + "@" + config.GroupDomain,
				"From":    backend.UserAddress{Medium: "sms", Address: message.From}.AsEmailAddress(),
				"Subject": user.ID + ": " + message.Body,
			},
			Body: "",
		}.Done()
	}
}
예제 #3
0
func GetAllExistingTwilioNumbers(cfg *periwinkle.Cfg) []string {
	// gets url for the numbers we own in the Twilio Account
	incomingNumURL := "https://api.twilio.com/2010-04-01/Accounts/" + cfg.TwilioAccountID + "/IncomingPhoneNumbers.json"

	client := &http.Client{}

	req, err := http.NewRequest("GET", incomingNumURL, nil)
	if err != nil {
		periwinkle.LogErr(locale.UntranslatedError(err))
		return nil
	}

	req.SetBasicAuth(cfg.TwilioAccountID, cfg.TwilioAuthToken)

	resp, err := client.Do(req)

	if err != nil {
		periwinkle.LogErr(locale.UntranslatedError(err))
		return nil
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		periwinkle.LogErr(locale.UntranslatedError(err))
		return nil
	}

	if resp.StatusCode != 200 {
		periwinkle.Logf("Response code %s", resp.Status)
		return nil
	}

	numbers := IncomingNumbers{}
	if err := json.Unmarshal(body, &numbers); err != nil {
		periwinkle.LogErr(locale.UntranslatedError(err))
		return nil
	}

	if len(numbers.PhoneNumbers) > 0 {

		existingNumbers := make([]string, len(numbers.PhoneNumbers))

		for i, num := range numbers.PhoneNumbers {
			existingNumbers[i] = num.Number
		}

		return existingNumbers

	} else {
		return nil
	}
}
예제 #4
0
파일: main.go 프로젝트: LukeShu/periwinkle
func main() {
	options := cmdutil.Docopt(usage)
	config := cmdutil.GetConfig(options["-c"].(string))

	conflict := config.DB.Do(func(tx *periwinkle.Tx) {
		err := backend.DbDrop(tx)
		if err != nil {
			periwinkle.LogErr(err)
			os.Exit(int(lsb.EXIT_FAILURE))
		}
	})
	if conflict != nil {
		periwinkle.LogErr(conflict)
		os.Exit(int(lsb.EXIT_FAILURE))
	}
}
예제 #5
0
파일: main.go 프로젝트: LukeShu/periwinkle
func main() {
	options := cmdutil.Docopt(usage)
	config := cmdutil.GetConfig(options["-c"].(string))

	conflict := config.DB.Do(func(tx *periwinkle.Tx) {
		/*
			err := backend.DbSchema(tx)
			if err != nil {
				periwinkle.Logf("Encountered an error while setting up the database schema, not attempting to seed data:")
				periwinkle.LogErr(err)
				os.Exit(int(lsb.EXIT_FAILURE))
			}

			err = backend.DbSeed(tx)
			if err != nil {
				periwinkle.Logf("Encountered an error while seeding the database:")
				periwinkle.LogErr(err)
				os.Exit(int(lsb.EXIT_FAILURE))
			}
		*/
		test.Test(config, tx)
	})
	if conflict != nil {
		periwinkle.LogErr(conflict)
		os.Exit(int(lsb.EXIT_FAILURE))
	}
}
예제 #6
0
파일: main.go 프로젝트: LukeShu/periwinkle
func main() {
	options := cmdutil.Docopt(usage)
	config := cmdutil.GetConfig(options["-c"].(string))

	var ret pp.ExitStatus = pp.EX_OK
	defer func() {
		if reason := recover(); reason != nil {
			const size = 64 << 10
			buf := make([]byte, size)
			buf = buf[:runtime.Stack(buf, false)]
			st := fmt.Sprintf("%[1]T(%#[1]v) => %[1]v\n\n%[2]s", reason, string(buf))
			periwinkle.Logf("%s", st)
			ret = pp.EX_UNAVAILABLE
		}
		pp.Exit(ret)
	}()

	conflict := config.DB.Do(func(transaction *periwinkle.Tx) {
		msg := pp.Get()

		recipient := msg.ORIGINAL_RECIPIENT()
		if recipient == "" {
			periwinkle.Logf("ORIGINAL_RECIPIENT must be set")
			ret = pp.EX_USAGE
			return
		}
		parts := strings.SplitN(recipient, "@", 2)
		user := parts[0]
		domain := "localhost"
		if len(parts) == 2 {
			domain = parts[1]
		}
		domain = strings.ToLower(domain)

		reader, err := msg.Reader()
		if err != nil {
			periwinkle.LogErr(err)
			ret = pp.EX_NOINPUT
			return
		}

		if handler, ok := config.DomainHandlers[domain]; ok {
			ret = handler(reader, user, transaction, config)
		} else {
			ret = config.DefaultDomainHandler(reader, recipient, transaction, config)
		}
	})
	if conflict != nil {
		ret = pp.EX_DATAERR
	}
}
예제 #7
0
파일: main.go 프로젝트: LukeShu/periwinkle
func main() {
	options := cmdutil.Docopt(usage)

	args := []string{}
	if options["ADDR_TYPE"] != nil {
		args = append(args, options["ADDR_TYPE"].(string))
	}
	if options["ADDR"] != nil {
		args = append(args, options["ADDR"].(string))
	}
	socket := parseArgs(args)

	config := cmdutil.GetConfig(options["-c"].(string))

	signals := make(chan os.Signal)
	signal.Notify(signals, syscall.SIGTERM, syscall.SIGHUP)

	periwinkle.Logf("Ready; listening")
	sd.Notify(false, "READY=1")

	done := make(chan uint8)
	server := httpapi.MakeServer(socket, config)
	server.Start()
	go func() {
		err := server.Wait()
		if err != nil {
			periwinkle.LogErr(err)
			done <- 1
		} else {
			done <- 0
		}
	}()

	for {
		select {
		case sig := <-signals:
			switch sig {
			case syscall.SIGTERM:
				sd.Notify(false, "STOPPING=1")
				server.Stop()
			case syscall.SIGHUP:
				sd.Notify(false, "RELOADING=1")
				// TODO: reload configuration file
				sd.Notify(false, "READY=1")
			}
		case status := <-done:
			os.Exit(int(status))
		}
	}
}
예제 #8
0
파일: main.go 프로젝트: LukeShu/periwinkle
func main() {
	options := cmdutil.Docopt(usage)
	config := cmdutil.GetConfig(options["-c"].(string))

	for {
		time.Sleep(time.Second)
		conflict := config.DB.Do(func(tx *periwinkle.Tx) {
			numbers := backend.GetAllUsedTwilioNumbers(tx)

			for _, number := range numbers {
				checkNumber(config, tx, number)
			}
		})
		if conflict != nil {
			periwinkle.LogErr(conflict)
		}
		lastPoll = time.Now().UTC()
	}
}
예제 #9
0
func Test(cfg *periwinkle.Cfg, db *periwinkle.Tx) {

	num := backend.TwilioNumber{
		Number: "+13346038139",
	}

	if err := db.Create(&num).Error; err != nil {
		panic(err)
	}

	user1 := backend.User{
		ID:        "alex",
		FullName:  "",
		Addresses: []backend.UserAddress{{Medium: "email", Address: "*****@*****.**", Confirmed: true}},
	}

	uerr := db.Create(&user1).Error
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
	}

	user2 := backend.User{
		ID:        "john",
		FullName:  "",
		Addresses: []backend.UserAddress{{Medium: "sms", Address: "+17656027006", Confirmed: true}, {Medium: "email", Address: "*****@*****.**", Confirmed: true}},
	}

	uerr = db.Create(&user2).Error
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
	}

	user3 := backend.User{
		ID:        "guntas",
		FullName:  "",
		Addresses: []backend.UserAddress{{Medium: "sms", Address: "+16166342620", Confirmed: true}},
	}

	uerr = db.Create(&user3).Error
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
	}
	existence := [2]int{2, 2}
	read := [2]int{2, 2}
	post := [3]int{1, 1, 1}
	join := [3]int{1, 1, 1}
	uerr = db.Create(&backend.Group{
		ID:                 "Purdue",
		ReadPublic:         read[0],
		ReadConfirmed:      read[1],
		ExistencePublic:    existence[0],
		ExistenceConfirmed: existence[1],
		PostPublic:         post[0],
		PostConfirmed:      post[1],
		PostMember:         post[2],
		JoinPublic:         join[0],
		JoinConfirmed:      join[1],
		JoinMember:         join[2],
		Subscriptions: []backend.Subscription{
			{AddressID: user1.Addresses[0].ID, Confirmed: true},
			{AddressID: user2.Addresses[0].ID, Confirmed: true},
			{AddressID: user2.Addresses[1].ID, Confirmed: true},
			{AddressID: user3.Addresses[0].ID, Confirmed: true},
		},
	}).Error
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
	}
}
예제 #10
0
파일: main.go 프로젝트: LukeShu/periwinkle
func parseArgs(args []string) net.Listener {
	var stype, saddr string

	switch len(args) {
	case 0:
		stype = "tcp"
		saddr = ":8080"
	case 1:
		switch args[0] {
		case "tcp", "tcp4", "tcp6":
			stype = args[0]
			saddr = ":8080"
		case "unix":
			stype = args[0]
			saddr = "./http.sock"
		case "fd":
			stype = args[0]
			saddr = "stdin"
		case "systemd", "stdin", "stdout", "stderr":
			stype = "fd"
			saddr = args[0]
		default:
			if strings.ContainsRune(args[0], '/') {
				stype = "unix"
			} else if _, err := strconv.Atoi(args[0]); err == nil {
				stype = "fd"
			} else {
				stype = "tcp"
			}
			saddr = args[0]
		}
	case 2:
		stype = args[0]
		saddr = args[1]
	default:
		periwinkle.Logf(usage, os.Args[0])
		os.Exit(int(lsb.EXIT_INVALIDARGUMENT))
	}

	var socket net.Listener
	var err locale.Error

	if stype == "fd" {
		switch saddr {
		case "systemd":
			socket, err = sdGetSocket()
		case "stdin":
			socket, err = listenfd(0, "/dev/stdin")
		case "stdout":
			socket, err = listenfd(1, "/dev/stdout")
		case "stderr":
			socket, err = listenfd(2, "/dev/stderr")
		default:
			n, uerr := strconv.Atoi(saddr)
			if uerr == nil {
				socket, err = listenfd(n, "/dev/fd/"+saddr)
			}
		}
	} else {
		var uerr error
		socket, uerr = net.Listen(stype, saddr)
		err = locale.UntranslatedError(uerr)
		if tcpsock, ok := socket.(*net.TCPListener); ok {
			socket = tcpKeepAliveListener{tcpsock}
		}
	}
	if err != nil {
		periwinkle.LogErr(err)
		os.Exit(int(lsb.EXIT_FAILURE))
	}
	return socket
}
예제 #11
0
파일: sms.go 프로젝트: LukeShu/periwinkle
func HandleSMS(r io.Reader, name string, db *periwinkle.Tx, cfg *periwinkle.Cfg) postfixpipe.ExitStatus {
	message, uerr := mail.ReadMessage(r)
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
		return postfixpipe.EX_NOINPUT
	}

	group := message.Header.Get("From")
	user := backend.GetUserByAddress(db, "sms", name)

	smsFrom := backend.GetTwilioNumberByUserAndGroup(db, user.ID, strings.Split(group, "@")[0])

	if smsFrom == "" {
		twilio_num := twilio.GetUnusedTwilioNumbersByUser(cfg, db, user.ID)
		if twilio_num == nil {
			new_num, err := twilio.NewPhoneNum(cfg)
			if err != nil {
				periwinkle.LogErr(err)
				return postfixpipe.EX_UNAVAILABLE
			}
			backend.AssignTwilioNumber(db, user.ID, strings.Split(group, "@")[0], new_num)
			smsFrom = new_num
		} else {
			backend.AssignTwilioNumber(db, user.ID, strings.Split(group, "@")[0], twilio_num[0])
			smsFrom = twilio_num[0]
		}
	}

	smsBody := message.Header.Get("Subject")
	//smsBody, err := ioutil.ReadAll(message.Body)
	//if err != nil {
	//	return "", err
	//}

	messagesURL := "https://api.twilio.com/2010-04-01/Accounts/" + cfg.TwilioAccountID + "/Messages.json"

	v := url.Values{}
	v.Set("From", smsFrom)
	v.Set("To", name)
	v.Set("Body", string(smsBody))
	v.Set("StatusCallback", cfg.WebRoot+"/callbacks/twilio-sms")
	//host,_ := os.Hostname()
	//v.Set("StatusCallback", "http://" + host + ":8080/callbacks/twilio-sms")
	client := &http.Client{}

	req, uerr := http.NewRequest("POST", messagesURL, bytes.NewBuffer([]byte(v.Encode())))
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
		return postfixpipe.EX_UNAVAILABLE
	}
	req.SetBasicAuth(cfg.TwilioAccountID, cfg.TwilioAuthToken)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	resp, uerr := client.Do(req)
	defer resp.Body.Close()
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
		return postfixpipe.EX_UNAVAILABLE
	}

	if resp.StatusCode != 200 && resp.StatusCode != 201 {
		return postfixpipe.EX_UNAVAILABLE
	}

	body, uerr := ioutil.ReadAll(resp.Body)
	if uerr != nil {
		periwinkle.LogErr(locale.UntranslatedError(uerr))
		return postfixpipe.EX_UNAVAILABLE
	}
	return postfixpipe.EX_OK
	tmessage := twilio.Message{}
	json.Unmarshal([]byte(body), &tmessage)
	_, err := TwilioSMSWaitForCallback(cfg, tmessage.Sid)
	if err != nil {
		periwinkle.LogErr(err)
		return postfixpipe.EX_UNAVAILABLE
	}
	return postfixpipe.EX_OK
}