cmd_vpn.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package main
  2. import (
  3. "fmt"
  4. "github.com/asaskevich/govalidator"
  5. "github.com/cad/ovpm"
  6. "github.com/cad/ovpm/api/pb"
  7. "github.com/cad/ovpm/errors"
  8. "github.com/sirupsen/logrus"
  9. "github.com/urfave/cli"
  10. )
  11. var vpnStatusCommand = cli.Command{
  12. Name: "status",
  13. Usage: "Show VPN status.",
  14. Aliases: []string{"s"},
  15. Action: func(c *cli.Context) error {
  16. // Use default port if no port is specified.
  17. daemonPort := ovpm.DefaultDaemonPort
  18. if port := c.GlobalInt("daemon-port"); port != 0 {
  19. daemonPort = port
  20. }
  21. // If dry run, then don't call the action, just preprocess.
  22. if c.GlobalBool("dry-run") {
  23. return nil
  24. }
  25. return vpnStatusAction(fmt.Sprintf("grpc://localhost:%d", daemonPort))
  26. },
  27. }
  28. var vpnInitCommand = cli.Command{
  29. Name: "init",
  30. Usage: "Initialize VPN server.",
  31. Aliases: []string{"i"},
  32. Flags: []cli.Flag{
  33. cli.StringFlag{
  34. Name: "hostname, s",
  35. Usage: "ip address or FQDN of the vpn server",
  36. },
  37. cli.StringFlag{
  38. Name: "port, p",
  39. Usage: "port number of the vpn server",
  40. Value: ovpm.DefaultVPNPort,
  41. },
  42. cli.BoolFlag{
  43. Name: "tcp, t",
  44. Usage: "use TCP for vpn protocol, instead of UDP",
  45. },
  46. cli.StringFlag{
  47. Name: "net, n",
  48. Usage: fmt.Sprintf("VPN network to give clients IP addresses from, in the CIDR form (default: %s)", ovpm.DefaultVPNNetwork),
  49. Value: ovpm.DefaultVPNNetwork,
  50. },
  51. cli.StringFlag{
  52. Name: "dns, d",
  53. Usage: fmt.Sprintf("DNS server to push to clients (default: %s)", ovpm.DefaultVPNDNS),
  54. },
  55. cli.StringFlag{
  56. Name: "keepalive-period",
  57. Usage: "Ping period to check if the remote peer is alive.",
  58. Value: ovpm.DefaultKeepalivePeriod,
  59. },
  60. cli.StringFlag{
  61. Name: "keepalive-timeout",
  62. Usage: "Ping timeout to assume that remote peer is down.",
  63. Value: ovpm.DefaultKeepaliveTimeout,
  64. },
  65. cli.BoolFlag{
  66. Name: "use-lzo, l",
  67. Usage: "Used to determine whether to use the deprecated lzo compression algorithm to support older clients. (default: false)",
  68. },
  69. },
  70. Action: func(c *cli.Context) error {
  71. action = "vpn:init"
  72. // Use default port if no port is specified.
  73. daemonPort := ovpm.DefaultDaemonPort
  74. if port := c.GlobalInt("daemon-port"); port != 0 {
  75. daemonPort = port
  76. }
  77. // Validate hostname.
  78. hostname := c.String("hostname")
  79. if govalidator.IsNull(hostname) || !govalidator.IsHost(hostname) {
  80. return errors.NotHostname(hostname)
  81. }
  82. // Set port number, if provided.
  83. port := c.String("port")
  84. if !govalidator.IsNumeric(port) {
  85. return errors.InvalidPort(port)
  86. }
  87. // Set proto if provided.
  88. proto := pb.VPNProto_UDP
  89. if c.Bool("tcp") {
  90. proto = pb.VPNProto_TCP
  91. }
  92. // Set ipblock if provided.
  93. netCIDR := c.String("net")
  94. if !govalidator.IsCIDR(netCIDR) {
  95. return errors.NotCIDR(netCIDR)
  96. }
  97. // Set DNS if provided.
  98. dnsAddr := ovpm.DefaultVPNDNS
  99. if !govalidator.IsIPv4(dnsAddr) {
  100. return errors.NotIPv4(dnsAddr)
  101. }
  102. // Set KeepalivePeriod if provided.
  103. keepalivePeriod := c.String("keepalive-period")
  104. if !govalidator.IsNumeric(keepalivePeriod) {
  105. return errors.NotValidKeepalivePeriod(keepalivePeriod)
  106. }
  107. // Set KeepaliveTimeout if provided.
  108. keepaliveTimeout := c.String("keepalive-timeout")
  109. if !govalidator.IsNumeric(keepaliveTimeout) {
  110. return errors.NotValidKeepaliveTimeout(keepaliveTimeout)
  111. }
  112. useLZO := c.Bool("use-lzo")
  113. // Ask for confirmation from the user about the destructive
  114. // changes that are about to happen.
  115. var uiConfirmed bool
  116. {
  117. var response string
  118. for {
  119. fmt.Println("This operation will cause invalidation of existing user certificates.")
  120. fmt.Println("After this opeartion, new client config files (.ovpn) should be generated for each existing user.")
  121. fmt.Println()
  122. fmt.Println("Are you sure ? (y/N)")
  123. _, err := fmt.Scanln(&response)
  124. if err != nil {
  125. logrus.Fatal(err)
  126. exit(1)
  127. return err
  128. }
  129. okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
  130. nokayResponses := []string{"n", "N", "no", "No", "NO"}
  131. if stringInSlice(response, okayResponses) {
  132. uiConfirmed = true
  133. break
  134. }
  135. if stringInSlice(response, nokayResponses) {
  136. uiConfirmed = false
  137. break
  138. }
  139. }
  140. }
  141. // Did user confirm the destructive changes?
  142. if !uiConfirmed {
  143. return errors.Unconfirmed("user decided to cancel")
  144. }
  145. // If dry run, then don't call the action, just preprocess.
  146. if c.GlobalBool("dry-run") {
  147. return nil
  148. }
  149. err := vpnInitAction(vpnInitParams{
  150. rpcServURLStr: fmt.Sprintf("grpc://localhost:%d", daemonPort),
  151. hostname: hostname,
  152. port: port,
  153. proto: proto,
  154. netCIDR: netCIDR,
  155. dnsAddr: c.String("dns"),
  156. keepalivePeriod: keepalivePeriod,
  157. keepaliveTimeout: keepaliveTimeout,
  158. useLZO: useLZO,
  159. })
  160. if err != nil {
  161. e, ok := err.(errors.Error)
  162. if ok {
  163. switch e.Code {
  164. case errors.ErrNotHostname:
  165. fmt.Printf("--hostname option requires a valid hostname: '%s' is not a hostname", c.String("hostname"))
  166. exit(1)
  167. return e
  168. }
  169. }
  170. return err
  171. }
  172. return nil
  173. },
  174. }
  175. var vpnUpdateCommand = cli.Command{
  176. Name: "update",
  177. Usage: "Update VPN server.",
  178. Aliases: []string{"u"},
  179. Flags: []cli.Flag{
  180. cli.StringFlag{
  181. Name: "net, n",
  182. Usage: fmt.Sprintf("VPN network to give clients IP addresses from, in the CIDR form (default: %s)", ovpm.DefaultVPNNetwork),
  183. },
  184. cli.StringFlag{
  185. Name: "dns, d",
  186. Usage: fmt.Sprintf("DNS server to push to clients (default: %s)", ovpm.DefaultVPNDNS),
  187. },
  188. },
  189. Action: func(c *cli.Context) error {
  190. action = "vpn:update"
  191. // Use default port if no port is specified.
  192. daemonPort := ovpm.DefaultDaemonPort
  193. if port := c.GlobalInt("daemon-port"); port != 0 {
  194. daemonPort = port
  195. }
  196. var netCIDR *string
  197. if net := c.String("net"); !govalidator.IsNull(net) {
  198. netCIDR = &net
  199. }
  200. var dnsAddr *string
  201. if dns := c.String("dns"); !govalidator.IsNull(dns) {
  202. dnsAddr = &dns
  203. }
  204. // If dry run, then don't call the action, just preprocess.
  205. if c.GlobalBool("dry-run") {
  206. return nil
  207. }
  208. return vpnUpdateAction(fmt.Sprintf("grpc://localhost:%d", daemonPort), netCIDR, dnsAddr)
  209. },
  210. }
  211. var vpnRestartCommand = cli.Command{
  212. Name: "restart",
  213. Usage: "Restart VPN server.",
  214. Aliases: []string{"r"},
  215. Action: func(c *cli.Context) error {
  216. // Use default port if no port is specified.
  217. daemonPort := ovpm.DefaultDaemonPort
  218. if port := c.GlobalInt("daemon-port"); port != 0 {
  219. daemonPort = port
  220. }
  221. // If dry run, then don't call the action, just preprocess.
  222. if c.GlobalBool("dry-run") {
  223. return nil
  224. }
  225. return vpnRestartAction(fmt.Sprintf("grpc://localhost:%d", daemonPort))
  226. },
  227. }
  228. func init() {
  229. app.Commands = append(app.Commands,
  230. cli.Command{
  231. Name: "vpn",
  232. Usage: "VPN Operations",
  233. Aliases: []string{"v"},
  234. Subcommands: []cli.Command{
  235. vpnStatusCommand,
  236. vpnInitCommand,
  237. vpnUpdateCommand,
  238. vpnRestartCommand,
  239. },
  240. },
  241. )
  242. }