main.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package main
  2. import (
  3. "bytes"
  4. "database/sql"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "os"
  9. "path"
  10. "regexp"
  11. "strings"
  12. "text/template"
  13. _ "github.com/go-sql-driver/mysql"
  14. "github.com/pin/tftp"
  15. "github.com/namsral/flag"
  16. )
  17. var (
  18. workdir, sepTeplateFilePath, freepbxConf string
  19. sepTeplateFile []byte
  20. db *sql.DB
  21. )
  22. //PhoneSetting struct
  23. type PhoneSetting struct {
  24. DisplayName, PhonePassword string
  25. PhoneNumber string
  26. }
  27. func getDBConnectionParams() (string, error) {
  28. var con string
  29. rex := regexp.MustCompile(`\["(.*)"\] = "(.*)";`)
  30. buf := new(bytes.Buffer)
  31. file, err := os.Open(freepbxConf)
  32. if err != nil {
  33. return con, err
  34. }
  35. defer file.Close()
  36. buf.ReadFrom(file)
  37. data := rex.FindAllStringSubmatch(buf.String(), -1)
  38. res := make(map[string]string)
  39. for _, kv := range data {
  40. k := kv[1]
  41. v := kv[2]
  42. res[k] = v
  43. }
  44. con = fmt.Sprintf("%s:%s@tcp(%s)/%s", res["AMPDBUSER"], res["AMPDBPASS"], res["AMPDBHOST"], res["AMPDBNAME"])
  45. fmt.Fprintf(os.Stderr, "%s:%s@tcp(%s)/%s", res["AMPDBUSER"], res["AMPDBPASS"], res["AMPDBHOST"], res["AMPDBNAME"])
  46. return con, nil
  47. }
  48. //Getting phone setting from freepbx database
  49. func getPhoneSetting(filename string) (*PhoneSetting, error) {
  50. var ps PhoneSetting
  51. filename = strings.TrimSuffix(filename, ".cnf.xml")
  52. query := `
  53. SELECT userman_users.displayname, sip.data, userman_users.default_extension
  54. FROM userman_users
  55. LEFT JOIN sip
  56. ON userman_users.default_extension=sip.id AND sip.keyword='secret'
  57. WHERE userman_users.fax=?
  58. ORDER BY userman_users.default_extension
  59. LIMIT 1
  60. `
  61. err := db.QueryRow(query, filename).Scan(&ps.DisplayName, &ps.PhonePassword, &ps.PhoneNumber)
  62. if err != nil {
  63. return &ps, err
  64. }
  65. return &ps, nil
  66. }
  67. //Send file to client
  68. func sendFile(file *bytes.Buffer, rf io.ReaderFrom) error {
  69. n, err := rf.ReadFrom(file)
  70. if err != nil {
  71. fmt.Fprintf(os.Stderr, "%v\n", err)
  72. return err
  73. }
  74. fmt.Printf("%d bytes sent\n", n)
  75. return nil
  76. }
  77. //Reading file in tftp dir. If file not found, returning empty file
  78. func readFile(filename string, rf io.ReaderFrom) error {
  79. var err error
  80. buf := new(bytes.Buffer)
  81. file, err := os.Open(path.Join(workdir, filename))
  82. if err != nil {
  83. fmt.Fprintf(os.Stderr, "%v\n", err)
  84. } else {
  85. buf.ReadFrom(file)
  86. }
  87. defer file.Close()
  88. err = sendFile(buf, rf)
  89. if err != nil {
  90. fmt.Fprintf(os.Stderr, "%v\n", err)
  91. return err
  92. }
  93. return nil
  94. }
  95. //Generating phone settings file use go-template
  96. func genFile(filename string, rf io.ReaderFrom) error {
  97. var (
  98. tpl bytes.Buffer
  99. err error
  100. )
  101. p, err := getPhoneSetting(filename)
  102. if err != nil {
  103. fmt.Fprintf(os.Stderr, "%v\n", err)
  104. return err
  105. }
  106. t := template.Must(template.New("sepTeplateFile").Parse(string(sepTeplateFile)))
  107. err = t.Execute(&tpl, p)
  108. if err != nil {
  109. fmt.Fprintf(os.Stderr, "%v\n", err)
  110. return err
  111. }
  112. err = sendFile(&tpl, rf)
  113. if err != nil {
  114. fmt.Fprintf(os.Stderr, "%v\n", err)
  115. return err
  116. }
  117. return nil
  118. }
  119. //Processing file request from a tftp client
  120. func readHandler(filename string, rf io.ReaderFrom) error {
  121. raddr := rf.(tftp.OutgoingTransfer).RemoteAddr()
  122. laddr := rf.(tftp.RequestPacketInfo).LocalIP()
  123. fmt.Println("RRQ from:", raddr.String(), "To:", laddr.String(), "File:", filename)
  124. sepFile, err := path.Match("SEP*.cnf.xml", filename)
  125. if err != nil {
  126. fmt.Fprintf(os.Stderr, "%v\n", err)
  127. return err
  128. }
  129. if sepFile {
  130. genFile(filename, rf)
  131. } else {
  132. readFile(filename, rf)
  133. }
  134. return nil
  135. }
  136. //Declaring cli flags
  137. func init() {
  138. flag.StringVar(&workdir, "workdir", "/tftpboot", "Set working directory")
  139. flag.StringVar(&sepTeplateFilePath, "sep_template_file", "./sep-cisco.cnf.xml.tpl", "Set path to sep template file")
  140. flag.StringVar(&freepbxConf, "freepbx_conf", "/etc/freepbx.conf", "Set path to freepbx db connection config file")
  141. }
  142. func main() {
  143. var err error
  144. flag.Parse()
  145. sepTeplateFile, err = ioutil.ReadFile(sepTeplateFilePath)
  146. if err != nil {
  147. fmt.Fprintf(os.Stderr, "%v\n", err)
  148. os.Exit(1)
  149. }
  150. //Getting params for db connection
  151. dbConnParams, err := getDBConnectionParams()
  152. if err != nil {
  153. fmt.Fprintf(os.Stderr, "%v\n", err)
  154. os.Exit(1)
  155. }
  156. //Connicting to db
  157. db, err = sql.Open("mysql", dbConnParams)
  158. if err != nil {
  159. fmt.Fprintf(os.Stderr, "Error on initializing database connection: %s\n", err)
  160. os.Exit(1)
  161. }
  162. //Checking db connection
  163. err = db.Ping()
  164. if err != nil {
  165. fmt.Fprintf(os.Stderr, "Error on database connection: %s\n", err)
  166. os.Exit(1)
  167. }
  168. db.SetMaxIdleConns(10)
  169. fmt.Println("Starting freepbx tftp server")
  170. s := tftp.NewServer(readHandler, nil)
  171. err = s.ListenAndServe(":69")
  172. if err != nil {
  173. fmt.Fprintf(os.Stderr, "server: %v\n", err)
  174. os.Exit(1)
  175. }
  176. }