package server import ( "bytes" "net" "net/http" "net/url" "strings" "time" "github.com/gin-gonic/gin" "github.com/h44z/wg-portal/internal/common" "github.com/h44z/wg-portal/internal/users" "github.com/h44z/wg-portal/internal/wireguard" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/tatsushid/go-fastping" csrf "github.com/utrack/gin-csrf" ) type LdapCreateForm struct { Emails string `form:"email" binding:"required"` Identifier string `form:"identifier" binding:"required,lte=20"` } func (s *Server) GetAdminEditPeer(c *gin.Context) { peer := s.peers.GetPeerByKey(c.Query("pkey")) currentSession, err := s.setFormInSession(c, peer) if err != nil { s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error()) return } c.HTML(http.StatusOK, "admin_edit_client.html", gin.H{ "Route": c.Request.URL.Path, "Alerts": GetFlashes(c), "Session": currentSession, "Static": s.getStaticData(), "Peer": currentSession.FormData.(wireguard.Peer), "EditableKeys": s.config.Core.EditableKeys, "Device": s.peers.GetDevice(currentSession.DeviceName), "DeviceNames": s.GetDeviceNames(), "AdminEmail": s.config.Core.AdminUser, "Csrf": csrf.GetToken(c), }) } func (s *Server) PostAdminEditPeer(c *gin.Context) { currentPeer := s.peers.GetPeerByKey(c.Query("pkey")) urlEncodedKey := url.QueryEscape(c.Query("pkey")) currentSession := GetSessionData(c) var formPeer wireguard.Peer if currentSession.FormData != nil { formPeer = currentSession.FormData.(wireguard.Peer) } if err := c.ShouldBind(&formPeer); err != nil { _ = s.updateFormInSession(c, formPeer) SetFlashMessage(c, "failed to bind form data: "+err.Error(), "danger") c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey+"&formerr=bind") return } // Clean list input formPeer.IPsStr = common.ListToString(common.ParseStringList(formPeer.IPsStr)) formPeer.AllowedIPsStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsStr)) formPeer.AllowedIPsSrvStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsSrvStr)) disabled := c.PostForm("isdisabled") != "" now := time.Now() if disabled && currentPeer.DeactivatedAt == nil { formPeer.DeactivatedAt = &now } else if !disabled { formPeer.DeactivatedAt = nil } // Update in database if err := s.UpdatePeer(formPeer, now); err != nil { _ = s.updateFormInSession(c, formPeer) SetFlashMessage(c, "failed to update user: "+err.Error(), "danger") c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey+"&formerr=update") return } SetFlashMessage(c, "changes applied successfully", "success") c.Redirect(http.StatusSeeOther, "/admin/peer/edit?pkey="+urlEncodedKey) } func (s *Server) GetAdminCreatePeer(c *gin.Context) { currentSession, err := s.setNewPeerFormInSession(c) if err != nil { s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error()) return } c.HTML(http.StatusOK, "admin_edit_client.html", gin.H{ "Route": c.Request.URL.Path, "Alerts": GetFlashes(c), "Session": currentSession, "Static": s.getStaticData(), "Peer": currentSession.FormData.(wireguard.Peer), "EditableKeys": s.config.Core.EditableKeys, "Device": s.peers.GetDevice(currentSession.DeviceName), "DeviceNames": s.GetDeviceNames(), "AdminEmail": s.config.Core.AdminUser, "Csrf": csrf.GetToken(c), }) } func (s *Server) PostAdminCreatePeer(c *gin.Context) { currentSession := GetSessionData(c) var formPeer wireguard.Peer if currentSession.FormData != nil { formPeer = currentSession.FormData.(wireguard.Peer) } if err := c.ShouldBind(&formPeer); err != nil { _ = s.updateFormInSession(c, formPeer) SetFlashMessage(c, "failed to bind form data: "+err.Error(), "danger") c.Redirect(http.StatusSeeOther, "/admin/peer/create?formerr=bind") return } // Clean list input formPeer.IPsStr = common.ListToString(common.ParseStringList(formPeer.IPsStr)) formPeer.AllowedIPsStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsStr)) formPeer.AllowedIPsSrvStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsSrvStr)) disabled := c.PostForm("isdisabled") != "" now := time.Now() if disabled { formPeer.DeactivatedAt = &now } if err := s.CreatePeer(currentSession.DeviceName, formPeer); err != nil { _ = s.updateFormInSession(c, formPeer) SetFlashMessage(c, "failed to add user: "+err.Error(), "danger") c.Redirect(http.StatusSeeOther, "/admin/peer/create?formerr=create") return } SetFlashMessage(c, "client created successfully", "success") c.Redirect(http.StatusSeeOther, "/admin") } func (s *Server) GetAdminCreateLdapPeers(c *gin.Context) { currentSession, err := s.setFormInSession(c, LdapCreateForm{Identifier: "Default"}) if err != nil { s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error()) return } c.HTML(http.StatusOK, "admin_create_clients.html", gin.H{ "Route": c.Request.URL.Path, "Alerts": GetFlashes(c), "Session": currentSession, "Static": s.getStaticData(), "Users": s.users.GetFilteredAndSortedUsers("lastname", "asc", ""), "FormData": currentSession.FormData.(LdapCreateForm), "Device": s.peers.GetDevice(currentSession.DeviceName), "DeviceNames": s.GetDeviceNames(), "Csrf": csrf.GetToken(c), }) } func (s *Server) PostAdminCreateLdapPeers(c *gin.Context) { currentSession := GetSessionData(c) var formData LdapCreateForm if currentSession.FormData != nil { formData = currentSession.FormData.(LdapCreateForm) } if err := c.ShouldBind(&formData); err != nil { _ = s.updateFormInSession(c, formData) SetFlashMessage(c, "failed to bind form data: "+err.Error(), "danger") c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=bind") return } emails := common.ParseStringList(formData.Emails) for i := range emails { // TODO: also check email addr for validity? if !strings.ContainsRune(emails[i], '@') { _ = s.updateFormInSession(c, formData) SetFlashMessage(c, "invalid email address: "+emails[i], "danger") c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=mail") return } } logrus.Infof("creating %d ldap peers", len(emails)) for i := range emails { if err := s.CreatePeerByEmail(currentSession.DeviceName, emails[i], formData.Identifier, false); err != nil { _ = s.updateFormInSession(c, formData) SetFlashMessage(c, "failed to add user: "+err.Error(), "danger") c.Redirect(http.StatusSeeOther, "/admin/peer/createldap?formerr=create") return } } SetFlashMessage(c, "client(s) created successfully", "success") c.Redirect(http.StatusSeeOther, "/admin/peer/createldap") } func (s *Server) GetAdminDeletePeer(c *gin.Context) { currentPeer := s.peers.GetPeerByKey(c.Query("pkey")) if err := s.DeletePeer(currentPeer); err != nil { s.GetHandleError(c, http.StatusInternalServerError, "Deletion error", err.Error()) return } SetFlashMessage(c, "peer deleted successfully", "success") c.Redirect(http.StatusSeeOther, "/admin") } func (s *Server) GetPeerQRCode(c *gin.Context) { peer := s.peers.GetPeerByKey(c.Query("pkey")) currentSession := GetSessionData(c) if !currentSession.IsAdmin && peer.Email != currentSession.Email { s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!") return } png, err := peer.GetQRCode() if err != nil { s.GetHandleError(c, http.StatusInternalServerError, "QRCode error", err.Error()) return } c.Data(http.StatusOK, "image/png", png) return } func (s *Server) GetPeerConfig(c *gin.Context) { peer := s.peers.GetPeerByKey(c.Query("pkey")) currentSession := GetSessionData(c) if !currentSession.IsAdmin && peer.Email != currentSession.Email { s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!") return } cfg, err := peer.GetConfigFile(s.peers.GetDevice(peer.DeviceName)) if err != nil { s.GetHandleError(c, http.StatusInternalServerError, "ConfigFile error", err.Error()) return } c.Header("Content-Disposition", "attachment; filename="+peer.GetConfigFileName()) c.Data(http.StatusOK, "application/config", cfg) return } func (s *Server) GetPeerConfigMail(c *gin.Context) { peer := s.peers.GetPeerByKey(c.Query("pkey")) currentSession := GetSessionData(c) if !currentSession.IsAdmin && peer.Email != currentSession.Email { s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!") return } if err := s.sendPeerConfigMail(peer); err != nil { s.GetHandleError(c, http.StatusInternalServerError, "Email error", err.Error()) return } SetFlashMessage(c, "mail sent successfully", "success") if strings.HasPrefix(c.Request.URL.Path, "/user") { c.Redirect(http.StatusSeeOther, "/user/profile") } else { c.Redirect(http.StatusSeeOther, "/admin") } } func (s *Server) GetPeerStatus(c *gin.Context) { peer := s.peers.GetPeerByKey(c.Query("pkey")) currentSession := GetSessionData(c) if !currentSession.IsAdmin && peer.Email != currentSession.Email { s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!") return } if peer.Peer == nil { // no peer means disabled c.JSON(http.StatusOK, false) return } isOnline := false ping := make(chan bool) defer close(ping) for _, cidr := range peer.GetIPAddresses() { ip, _, _ := net.ParseCIDR(cidr) var ra *net.IPAddr if common.IsIPv6(ip.String()) { ra, _ = net.ResolveIPAddr("ip6:ipv6-icmp", ip.String()) } else { ra, _ = net.ResolveIPAddr("ip4:icmp", ip.String()) } p := fastping.NewPinger() p.AddIPAddr(ra) p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) { ping <- true p.Stop() } p.OnIdle = func() { ping <- false p.Stop() } p.MaxRTT = 500 * time.Millisecond p.RunLoop() if <-ping { isOnline = true break } } c.JSON(http.StatusOK, isOnline) return } func (s *Server) GetAdminSendEmails(c *gin.Context) { currentSession := GetSessionData(c) if !currentSession.IsAdmin { s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!") return } peers := s.peers.GetActivePeers(currentSession.DeviceName) for _, peer := range peers { if err := s.sendPeerConfigMail(peer); err != nil { s.GetHandleError(c, http.StatusInternalServerError, "Email error", err.Error()) return } } SetFlashMessage(c, "emails sent successfully", "success") c.Redirect(http.StatusSeeOther, "/admin") } func (s *Server) sendPeerConfigMail(peer wireguard.Peer) error { user := s.users.GetUser(peer.Email) cfg, err := peer.GetConfigFile(s.peers.GetDevice(peer.DeviceName)) if err != nil { return errors.Wrap(err, "failed to get config file") } png, err := peer.GetQRCode() if err != nil { return errors.Wrap(err, "failed to get qr-code") } // Apply mail template qrcodeFileName := "wireguard-qrcode.png" var tplBuff bytes.Buffer if err := s.mailTpl.Execute(&tplBuff, struct { Peer wireguard.Peer User *users.User QrcodePngName string PortalUrl string }{ Peer: peer, User: user, QrcodePngName: qrcodeFileName, PortalUrl: s.config.Core.ExternalUrl, }); err != nil { return errors.Wrap(err, "failed to execute mail template") } // Send mail attachments := []common.MailAttachment{ { Name: peer.GetConfigFileName(), ContentType: "application/config", Data: bytes.NewReader(cfg), }, { Name: qrcodeFileName, ContentType: "image/png", Data: bytes.NewReader(png), Embedded: true, }, { Name: qrcodeFileName, ContentType: "image/png", Data: bytes.NewReader(png), }, } if err := common.SendEmailWithAttachments(s.config.Email, s.config.Core.MailFrom, "", "WireGuard VPN Configuration", "Your mail client does not support HTML. Please find the configuration attached to this mail.", tplBuff.String(), []string{peer.Email}, attachments); err != nil { return errors.Wrap(err, "failed to send email") } return nil } func (s *Server) GetUserCreatePeer(c *gin.Context) { currentSession, err := s.setNewPeerFormInSession(c) if err != nil { s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error()) return } c.HTML(http.StatusOK, "user_create_client.html", gin.H{ "Route": c.Request.URL.Path, "Alerts": GetFlashes(c), "Session": currentSession, "Static": s.getStaticData(), "Peer": currentSession.FormData.(wireguard.Peer), "EditableKeys": s.config.Core.EditableKeys, "Device": s.peers.GetDevice(currentSession.DeviceName), "DeviceNames": s.GetDeviceNames(), "AdminEmail": s.config.Core.AdminUser, "Csrf": csrf.GetToken(c), }) } func (s *Server) PostUserCreatePeer(c *gin.Context) { currentSession := GetSessionData(c) var formPeer wireguard.Peer if currentSession.FormData != nil { formPeer = currentSession.FormData.(wireguard.Peer) } formPeer.Email = currentSession.Email; formPeer.Identifier = currentSession.Email; formPeer.DeviceType = wireguard.DeviceTypeServer; formPeer.PrivateKey = ""; if err := c.ShouldBind(&formPeer); err != nil { _ = s.updateFormInSession(c, formPeer) SetFlashMessage(c, "failed to bind form data: "+err.Error(), "danger") c.Redirect(http.StatusSeeOther, "/user/peer/create?formerr=bind") return } disabled := c.PostForm("isdisabled") != "" now := time.Now() if disabled { formPeer.DeactivatedAt = &now } if err := s.CreatePeer(currentSession.DeviceName, formPeer); err != nil { _ = s.updateFormInSession(c, formPeer) SetFlashMessage(c, "failed to add user: "+err.Error(), "danger") c.Redirect(http.StatusSeeOther, "/user/peer/create?formerr=create") return } SetFlashMessage(c, "client created successfully", "success") c.Redirect(http.StatusSeeOther, "/user/profile") } func (s *Server) GetUserEditPeer(c *gin.Context) { peer := s.peers.GetPeerByKey(c.Query("pkey")) currentSession, err := s.setFormInSession(c, peer) if err != nil { s.GetHandleError(c, http.StatusInternalServerError, "Session error", err.Error()) return } if peer.Email != currentSession.Email { s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!") return; } c.HTML(http.StatusOK, "user_edit_client.html", gin.H{ "Route": c.Request.URL.Path, "Alerts": GetFlashes(c), "Session": currentSession, "Static": s.getStaticData(), "Peer": currentSession.FormData.(wireguard.Peer), "EditableKeys": s.config.Core.EditableKeys, "Device": s.peers.GetDevice(currentSession.DeviceName), "DeviceNames": s.GetDeviceNames(), "AdminEmail": s.config.Core.AdminUser, "Csrf": csrf.GetToken(c), }) } func (s *Server) PostUserEditPeer(c *gin.Context) { currentPeer := s.peers.GetPeerByKey(c.Query("pkey")) urlEncodedKey := url.QueryEscape(c.Query("pkey")) currentSession := GetSessionData(c) if currentPeer.Email != currentSession.Email { s.GetHandleError(c, http.StatusUnauthorized, "No permissions", "You don't have permissions to view this resource!") return; } disabled := c.PostForm("isdisabled") != "" now := time.Now() if disabled && currentPeer.DeactivatedAt == nil { currentPeer.DeactivatedAt = &now } // Update in database if err := s.UpdatePeer(currentPeer, now); err != nil { _ = s.updateFormInSession(c, currentPeer) SetFlashMessage(c, "failed to update user: "+err.Error(), "danger") c.Redirect(http.StatusSeeOther, "/user/peer/edit?pkey="+urlEncodedKey+"&formerr=update") return } SetFlashMessage(c, "changes applied successfully", "success") c.Redirect(http.StatusSeeOther, "/user/peer/edit?pkey="+urlEncodedKey) }