first commit
This commit is contained in:
		
						commit
						b7ff98764b
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					config.json
 | 
				
			||||||
							
								
								
									
										19
									
								
								License.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								License.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					Copyright (c) 2021 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					# Asterisk Outgoing Call API
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Starts an outgoing call based on provided paramters according to the https://wiki.asterisk.org/wiki/display/AST/Asterisk+Call+Files call file format. View https://www.voip-info.org/asterisk-auto-dial-out/ for more details about the call file format. I created this to make my phone ring when my cell phone rings via a tasker profile. Should be useful for many additional things though.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Accepted parameters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- token: The API token to authenticate with the server.
 | 
				
			||||||
 | 
					- channel: Channel to use for the call.
 | 
				
			||||||
 | 
					- caller_id: Caller ID, Please note: It may not work if you do not respect the format: CallerID: “Some Name” <1234>
 | 
				
			||||||
 | 
					- wait_time: Seconds to wait for an answer. Default is 45.
 | 
				
			||||||
 | 
					- max_retries: Number of retries before failing (not including the initial attempt, e.g. 0 = total of 1 attempt to make the call). Default is 0.
 | 
				
			||||||
 | 
					- retry_time: Seconds between retries, Don’t hammer an unavailable phone. The default is 300 (5 min).
 | 
				
			||||||
 | 
					- account: Set the account code to use.
 | 
				
			||||||
 | 
					- application: Asterisk Application to run (use instead of specifying context, extension and priority).
 | 
				
			||||||
 | 
					- data: The options to be passed to application.
 | 
				
			||||||
 | 
					- context: Context in extensions.conf
 | 
				
			||||||
 | 
					- extension: Extension definition in extensions.conf
 | 
				
			||||||
 | 
					- priority: Priority of extension to start with.
 | 
				
			||||||
 | 
					- set_var: Set of variables to set in url query format.
 | 
				
			||||||
 | 
					- archive: Yes/No – Move to subdir “outgoing_done” with “Status: value”, where value can be Completed, Expired or Failed.
 | 
				
			||||||
 | 
					- schedule: Schedule call for a later date/time. Can be natrual language input as parsed by https://github.com/olebedev/when 
 | 
				
			||||||
							
								
								
									
										87
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/user"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reference from:
 | 
				
			||||||
 | 
					// https://wiki.asterisk.org/wiki/display/AST/Asterisk+Call+Files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Config all configurations for this application.
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						HTTPBind              string            `json:"http_bind"`
 | 
				
			||||||
 | 
						HTTPPort              uint              `json:"http_port"`
 | 
				
			||||||
 | 
						HTTPDebug             bool              `json:"http_debug"`
 | 
				
			||||||
 | 
						HTTPSystemDSocket     bool              `json:"http_systemd_socket"`
 | 
				
			||||||
 | 
						AsteriskSpoolDir      string            `json:"asterisk_spool_dir"`
 | 
				
			||||||
 | 
						DefaultChannel        string            `json:"default_channel"`
 | 
				
			||||||
 | 
						DefaultCallerId       string            `json:"default_caller_id"`
 | 
				
			||||||
 | 
						DefaultWaitTime       uint64            `json:"default_wait_time"` // 5 seconds per ring.
 | 
				
			||||||
 | 
						DefaultMaxRetries     uint64            `json:"default_max_retries"`
 | 
				
			||||||
 | 
						DefaultRetryTime      uint64            `json:"default_retry_time"`
 | 
				
			||||||
 | 
						DefaultAccount        string            `json:"default_account"`
 | 
				
			||||||
 | 
						DefaultApplication    string            `json:"default_application"`
 | 
				
			||||||
 | 
						DefaultData           string            `json:"default_data"`
 | 
				
			||||||
 | 
						PreventAPIApplication bool              `json:"prevent_api_application"` // For security, prevent applications from being executed via API call.
 | 
				
			||||||
 | 
						DefaultContext        string            `json:"default_context"`
 | 
				
			||||||
 | 
						DefaultExtension      string            `json:"default_extension"`
 | 
				
			||||||
 | 
						DefaultPriority       string            `json:"default_priority"`
 | 
				
			||||||
 | 
						DefaultSetVar         map[string]string `json:"default_set_var"`
 | 
				
			||||||
 | 
						DefaultArchive        bool              `json:"default_archive"`
 | 
				
			||||||
 | 
						APIToken              string            `json:"api_token"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadConfig read the configuration file into the config structure of the app.
 | 
				
			||||||
 | 
					func (a *App) ReadConfig() {
 | 
				
			||||||
 | 
						// Get our current user for use in determining the home path.
 | 
				
			||||||
 | 
						usr, err := user.Current()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Different configuration file paths.
 | 
				
			||||||
 | 
						localConfig, _ := filepath.Abs("./config.json")
 | 
				
			||||||
 | 
						homeDirConfig := usr.HomeDir + "/.config/asterisk-outgoing-call-api/config.json"
 | 
				
			||||||
 | 
						etcConfig := "/etc/asterisk/outgoing-call-api.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Store defaults first.
 | 
				
			||||||
 | 
						app.config = Config{
 | 
				
			||||||
 | 
							HTTPPort:              9747,
 | 
				
			||||||
 | 
							HTTPDebug:             false,
 | 
				
			||||||
 | 
							HTTPSystemDSocket:     false,
 | 
				
			||||||
 | 
							AsteriskSpoolDir:      "/var/spool/asterisk",
 | 
				
			||||||
 | 
							PreventAPIApplication: true,
 | 
				
			||||||
 | 
							DefaultArchive:        false,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Determine which config file to use.
 | 
				
			||||||
 | 
						var configFile string
 | 
				
			||||||
 | 
						if _, err := os.Stat(app.flags.ConfigPath); err == nil && app.flags.ConfigPath != "" {
 | 
				
			||||||
 | 
							configFile = app.flags.ConfigPath
 | 
				
			||||||
 | 
						} else if _, err := os.Stat(localConfig); err == nil {
 | 
				
			||||||
 | 
							configFile = localConfig
 | 
				
			||||||
 | 
						} else if _, err := os.Stat(homeDirConfig); err == nil {
 | 
				
			||||||
 | 
							configFile = homeDirConfig
 | 
				
			||||||
 | 
						} else if _, err := os.Stat(etcConfig); err == nil {
 | 
				
			||||||
 | 
							configFile = etcConfig
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							log.Fatal("Unable to find a configuration file.")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Read the config file.
 | 
				
			||||||
 | 
						jsonFile, err := ioutil.ReadFile(configFile)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("Error reading JSON file: %v\n", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse the config file into the configuration structure.
 | 
				
			||||||
 | 
						err = json.Unmarshal(jsonFile, &app.config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("Error parsing JSON file: %v\n", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								example.call
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								example.call
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					Channel: pjsip/103
 | 
				
			||||||
 | 
					WaitTime: 15
 | 
				
			||||||
 | 
					Context: Phone-Ring-Dummy-Answer
 | 
				
			||||||
 | 
					Extension: talk
 | 
				
			||||||
 | 
					Priority: 1
 | 
				
			||||||
 | 
					Archive: no
 | 
				
			||||||
							
								
								
									
										4
									
								
								extension.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								extension.conf
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					[Phone-Ring-Dummy-Answer]
 | 
				
			||||||
 | 
					exten => talk,1,Answer()
 | 
				
			||||||
 | 
					 same => n,Playback(all-your-base)
 | 
				
			||||||
 | 
					 same => n,Hangup()
 | 
				
			||||||
							
								
								
									
										45
									
								
								flags.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								flags.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Flags are command line tick options.
 | 
				
			||||||
 | 
					type Flags struct {
 | 
				
			||||||
 | 
						ConfigPath string
 | 
				
			||||||
 | 
						HTTPBind   string
 | 
				
			||||||
 | 
						HTTPPort   uint
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Init configures the golang flags and parses the command line provided options.
 | 
				
			||||||
 | 
					func (f *Flags) Init() {
 | 
				
			||||||
 | 
						flag.Usage = func() {
 | 
				
			||||||
 | 
							fmt.Printf("asterisk-outgoing-call-api: Make an outgoing call via an API call.\n\nUsage:\n")
 | 
				
			||||||
 | 
							flag.PrintDefaults()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var printVersion bool
 | 
				
			||||||
 | 
						flag.BoolVar(&printVersion, "v", false, "Print version")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var usage string
 | 
				
			||||||
 | 
						usage = "Load configuration from file."
 | 
				
			||||||
 | 
						flag.StringVar(&f.ConfigPath, "config", "", usage)
 | 
				
			||||||
 | 
						flag.StringVar(&f.ConfigPath, "c", "", usage+" (shorthand)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						usage = "Bind address for http server"
 | 
				
			||||||
 | 
						flag.StringVar(&f.HTTPBind, "http-bind", "", usage)
 | 
				
			||||||
 | 
						flag.StringVar(&f.HTTPBind, "b", "", usage+" (shorthand)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						usage = "Bind port for http server"
 | 
				
			||||||
 | 
						flag.UintVar(&f.HTTPPort, "http-port", 0, usage)
 | 
				
			||||||
 | 
						flag.UintVar(&f.HTTPPort, "p", 0, usage+" (shorthand)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag.Parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if printVersion {
 | 
				
			||||||
 | 
							fmt.Println("asterisk-outgoing-call-api: 0.1")
 | 
				
			||||||
 | 
							os.Exit(0)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					module github.com/GRMrGecko/asterisk-outgoing-call-api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
 | 
				
			||||||
 | 
						github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										16
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE=
 | 
				
			||||||
 | 
					github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8=
 | 
				
			||||||
 | 
					github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
 | 
				
			||||||
 | 
					github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 h1:JYoQR67E1vv1WGoeW8DkdFs7vrIEe/5wP+qJItd5tUE=
 | 
				
			||||||
 | 
					github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
							
								
								
									
										357
									
								
								http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								http.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,357 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"math/rand"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/coreos/go-systemd/activation"
 | 
				
			||||||
 | 
						"github.com/olebedev/when"
 | 
				
			||||||
 | 
						"github.com/olebedev/when/rules/common"
 | 
				
			||||||
 | 
						"github.com/olebedev/when/rules/en"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HTTPServer the http server structure.
 | 
				
			||||||
 | 
					type HTTPServer struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Common strings.
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						APIOK  = "ok"
 | 
				
			||||||
 | 
						APIERR = "error"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// APIGeneralResp General response to API requests.
 | 
				
			||||||
 | 
					type APIGeneralResp struct {
 | 
				
			||||||
 | 
						Status string `json:"status"`
 | 
				
			||||||
 | 
						Error  string `json:"error"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// JSONResponse Takes a golang structure and converts it to a JSON object for response.
 | 
				
			||||||
 | 
					func (s *HTTPServer) JSONResponse(w http.ResponseWriter, resp interface{}) {
 | 
				
			||||||
 | 
						// Encode response as json.
 | 
				
			||||||
 | 
						js, err := json.Marshal(resp)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// Error should not happen normally...
 | 
				
			||||||
 | 
							http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If no err, we can set content type header and send response.
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
						w.Write(js)
 | 
				
			||||||
 | 
						w.Write([]byte{'\n'})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// APISendGeneralResp Send a standard response.
 | 
				
			||||||
 | 
					func (s *HTTPServer) APISendGeneralResp(w http.ResponseWriter, status, err string) {
 | 
				
			||||||
 | 
						resp := APIGeneralResp{}
 | 
				
			||||||
 | 
						resp.Status = status
 | 
				
			||||||
 | 
						resp.Error = err
 | 
				
			||||||
 | 
						s.JSONResponse(w, resp)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// registerHandlers HTTP server handlers.
 | 
				
			||||||
 | 
					func (s *HTTPServer) registerHandlers(r *http.ServeMux) {
 | 
				
			||||||
 | 
						// For this project, we only handle requests to /.
 | 
				
			||||||
 | 
						r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
							// Parse form data.
 | 
				
			||||||
 | 
							err := r.ParseMultipartForm(32 << 20)
 | 
				
			||||||
 | 
							if err == http.ErrNotMultipart {
 | 
				
			||||||
 | 
								err = r.ParseForm()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Println(err)
 | 
				
			||||||
 | 
								s.APISendGeneralResp(w, APIERR, "Bad request")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Verify we are authorized.
 | 
				
			||||||
 | 
							if r.Form.Get("token") != app.config.APIToken {
 | 
				
			||||||
 | 
								s.APISendGeneralResp(w, APIERR, "Unauthorized")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get call details.
 | 
				
			||||||
 | 
							channel := r.Form.Get("channel")
 | 
				
			||||||
 | 
							if channel == "" {
 | 
				
			||||||
 | 
								channel = app.config.DefaultChannel
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							callerId := r.Form.Get("caller_id")
 | 
				
			||||||
 | 
							if callerId == "" {
 | 
				
			||||||
 | 
								callerId = app.config.DefaultCallerId
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							waitTime, err := strconv.ParseUint(r.Form.Get("wait_time"), 10, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								waitTime = app.config.DefaultWaitTime
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							maxRetries, err := strconv.ParseUint(r.Form.Get("max_retries"), 10, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								maxRetries = app.config.DefaultMaxRetries
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							retryTime, err := strconv.ParseUint(r.Form.Get("retry_time"), 10, 64)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								retryTime = app.config.DefaultRetryTime
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							account := r.Form.Get("account")
 | 
				
			||||||
 | 
							if account == "" {
 | 
				
			||||||
 | 
								account = app.config.DefaultCallerId
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							application := r.Form.Get("application")
 | 
				
			||||||
 | 
							if application == "" || app.config.PreventAPIApplication {
 | 
				
			||||||
 | 
								application = app.config.DefaultApplication
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							data := r.Form.Get("data")
 | 
				
			||||||
 | 
							if data == "" || app.config.PreventAPIApplication {
 | 
				
			||||||
 | 
								data = app.config.DefaultData
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							context := r.Form.Get("context")
 | 
				
			||||||
 | 
							if context == "" {
 | 
				
			||||||
 | 
								context = app.config.DefaultContext
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							extension := r.Form.Get("extension")
 | 
				
			||||||
 | 
							if context == "" {
 | 
				
			||||||
 | 
								extension = app.config.DefaultExtension
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							priority := r.Form.Get("priority")
 | 
				
			||||||
 | 
							if context == "" {
 | 
				
			||||||
 | 
								priority = app.config.DefaultPriority
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							setVar := make(map[string]string)
 | 
				
			||||||
 | 
							parsed, err := url.ParseQuery(r.Form.Get("set_var"))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								setVar = app.config.DefaultSetVar
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								for key, value := range parsed {
 | 
				
			||||||
 | 
									setVar[key] = value[0]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							archiveVal := strings.ToLower(r.Form.Get("archive"))
 | 
				
			||||||
 | 
							archive := false
 | 
				
			||||||
 | 
							if archiveVal == "true" || archiveVal == "yes" {
 | 
				
			||||||
 | 
								archive = true
 | 
				
			||||||
 | 
							} else if archiveVal != "false" && archiveVal != "no" {
 | 
				
			||||||
 | 
								archive = app.config.DefaultArchive
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							schedule := r.Form.Get("schedule")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if channel == "" || (application == "" && context == "") {
 | 
				
			||||||
 | 
								s.APISendGeneralResp(w, APIERR, "Required options not set")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Setup call file details.
 | 
				
			||||||
 | 
							outgoingCallName := "outgoing-call-" + strconv.Itoa(rand.Int())
 | 
				
			||||||
 | 
							spoolFileName := path.Join(app.config.AsteriskSpoolDir, outgoingCallName)
 | 
				
			||||||
 | 
							outgoingFileName := path.Join(app.config.AsteriskSpoolDir, "outgoing", outgoingCallName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							callFile, err := os.Create(spoolFileName)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Println(err)
 | 
				
			||||||
 | 
								s.APISendGeneralResp(w, APIERR, "Unable to create call file")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Write call details.
 | 
				
			||||||
 | 
							callFile.WriteString("Channel: " + channel + "\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if callerId != "" {
 | 
				
			||||||
 | 
								callFile.WriteString("Callerid: " + callerId + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if waitTime != 0 {
 | 
				
			||||||
 | 
								callFile.WriteString("WaitTime: " + strconv.FormatUint(waitTime, 10) + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if maxRetries != 0 {
 | 
				
			||||||
 | 
								callFile.WriteString("MaxRetries: " + strconv.FormatUint(maxRetries, 10) + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if retryTime != 0 {
 | 
				
			||||||
 | 
								callFile.WriteString("RetryTime: " + strconv.FormatUint(retryTime, 10) + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if account != "" {
 | 
				
			||||||
 | 
								callFile.WriteString("Account: " + account + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if application != "" {
 | 
				
			||||||
 | 
								callFile.WriteString("Application: " + application + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if data != "" {
 | 
				
			||||||
 | 
								callFile.WriteString("Data: " + data + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if context != "" {
 | 
				
			||||||
 | 
								callFile.WriteString("Context: " + context + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if extension != "" {
 | 
				
			||||||
 | 
								callFile.WriteString("Extension: " + extension + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if priority != "" {
 | 
				
			||||||
 | 
								callFile.WriteString("Priority: " + priority + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for key, value := range setVar {
 | 
				
			||||||
 | 
								callFile.WriteString("Setvar: " + key + "=" + value + "\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if archive {
 | 
				
			||||||
 | 
								callFile.WriteString("Archive: yes\n")
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								callFile.WriteString("Archive: no\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							callFile.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if schedule != "" {
 | 
				
			||||||
 | 
								now := time.Now()
 | 
				
			||||||
 | 
								w := when.New(nil)
 | 
				
			||||||
 | 
								w.Add(en.All...)
 | 
				
			||||||
 | 
								w.Add(common.All...)
 | 
				
			||||||
 | 
								parsedTime, _ := w.Parse(schedule, now)
 | 
				
			||||||
 | 
								if parsedTime == nil {
 | 
				
			||||||
 | 
									parsedTime = new(when.Result)
 | 
				
			||||||
 | 
									parsedTime.Time = now
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								os.Chtimes(spoolFileName, parsedTime.Time, parsedTime.Time)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Add call to the outgoing call queue.
 | 
				
			||||||
 | 
							err = os.Rename(spoolFileName, outgoingFileName)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Println(err)
 | 
				
			||||||
 | 
								s.APISendGeneralResp(w, APIERR, "Unable to move call file into outgoing directory")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Send final response.
 | 
				
			||||||
 | 
							s.APISendGeneralResp(w, APIOK, "")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func HTTPServe() {
 | 
				
			||||||
 | 
						// Used to reset the app quit timeout for systemd sockets.
 | 
				
			||||||
 | 
						var timeoutReset chan struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create the server.
 | 
				
			||||||
 | 
						httpServer := new(HTTPServer)
 | 
				
			||||||
 | 
						app.httpServer = httpServer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup the handlers.
 | 
				
			||||||
 | 
						r := http.NewServeMux()
 | 
				
			||||||
 | 
						httpServer.registerHandlers(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The http server handler will be the mux router by default.
 | 
				
			||||||
 | 
						var handler http.Handler
 | 
				
			||||||
 | 
						handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
							if app.config.HTTPSystemDSocket {
 | 
				
			||||||
 | 
								timeoutReset <- struct{}{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if app.config.HTTPDebug {
 | 
				
			||||||
 | 
								log.Println(req.Method + " " + req.URL.String())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r.ServeHTTP(w, req)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Determine if we're using a systemd socket activation or just a standard listen.
 | 
				
			||||||
 | 
						if app.config.HTTPSystemDSocket {
 | 
				
			||||||
 | 
							done := make(chan struct{})
 | 
				
			||||||
 | 
							quit := make(chan os.Signal, 1)
 | 
				
			||||||
 | 
							timeoutReset = make(chan struct{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// On signal, gracefully shut down the server and wait 5
 | 
				
			||||||
 | 
							// seconds for current connection to stop.
 | 
				
			||||||
 | 
							signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Pull existing listener from systemd.
 | 
				
			||||||
 | 
							listeners, err := activation.Listeners()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Panicf("Cannot retrieve listeners: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If we already have a asterisk-outgoing-call-api running, then we shouldn't start...
 | 
				
			||||||
 | 
							if len(listeners) != 1 {
 | 
				
			||||||
 | 
								log.Panicf("Unexpected number of socket activation (%d != 1)", len(listeners))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							server := &http.Server{
 | 
				
			||||||
 | 
								Handler: handler,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Upon signal, close out existing connection and quit.
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								<-quit
 | 
				
			||||||
 | 
								log.Println("Server is shutting down")
 | 
				
			||||||
 | 
								ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | 
				
			||||||
 | 
								defer cancel()
 | 
				
			||||||
 | 
								server.SetKeepAlivesEnabled(false)
 | 
				
			||||||
 | 
								if err := server.Shutdown(ctx); err != nil {
 | 
				
			||||||
 | 
									log.Panicf("Cannot gracefully shut down the server: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								close(done)
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 30 minute time out if no connection is received.
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								for {
 | 
				
			||||||
 | 
									select {
 | 
				
			||||||
 | 
									case <-timeoutReset:
 | 
				
			||||||
 | 
									case <-time.After(30 * time.Minute):
 | 
				
			||||||
 | 
										close(quit)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Listen on existing systemd socket.
 | 
				
			||||||
 | 
							server.Serve(listeners[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Wait for existing connections befor exiting.
 | 
				
			||||||
 | 
							<-done
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Get the configuration.
 | 
				
			||||||
 | 
							httpBind := app.config.HTTPBind
 | 
				
			||||||
 | 
							httpPort := app.config.HTTPPort
 | 
				
			||||||
 | 
							if app.flags.HTTPBind != "" {
 | 
				
			||||||
 | 
								httpBind = app.flags.HTTPBind
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if app.flags.HTTPPort != 0 {
 | 
				
			||||||
 | 
								httpPort = app.flags.HTTPPort
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Start the server.
 | 
				
			||||||
 | 
							log.Println("Starting the http server on port", httpPort)
 | 
				
			||||||
 | 
							err := http.ListenAndServe(fmt.Sprintf("%s:%d", httpBind, httpPort), handler)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"math/rand"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// App is the standard structure that allows different parts of the application to access common parameters/configuration.
 | 
				
			||||||
 | 
					type App struct {
 | 
				
			||||||
 | 
						flags      *Flags
 | 
				
			||||||
 | 
						httpServer *HTTPServer
 | 
				
			||||||
 | 
						config     Config
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var app *App
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						// We use rand for file naming, best set seed at start.
 | 
				
			||||||
 | 
						rand.Seed(time.Now().UnixNano())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app = new(App)
 | 
				
			||||||
 | 
						app.flags = new(Flags)
 | 
				
			||||||
 | 
						app.flags.Init()
 | 
				
			||||||
 | 
						app.ReadConfig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						HTTPServe()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user