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) }) }
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() } }
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 } }
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)) } }
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)) } }
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 } }
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)) } } }
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() } }
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)) } }
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 }
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 }