/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Reflection; using log4net; using Mono.Addins; using Nini.Config; using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Framework.Client; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; namespace OpenSim.Services.Connectors.SimianGrid { /// /// Avatar profile flags /// [Flags] public enum ProfileFlags : uint { AllowPublish = 1, MaturePublish = 2, Identified = 4, Transacted = 8, Online = 16 } /// /// Connects avatar profile and classified queries to the SimianGrid /// backend /// [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")] public class SimianProfiles : INonSharedRegionModule { private static readonly ILog m_log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType); private string m_serverUrl = String.Empty; #region INonSharedRegionModule public Type ReplaceableInterface { get { return null; } } public void RegionLoaded(Scene scene) { } public void Close() { } public SimianProfiles() { } public string Name { get { return "SimianProfiles"; } } public void AddRegion(Scene scene) { if (!String.IsNullOrEmpty(m_serverUrl)) { CheckEstateManager(scene); scene.EventManager.OnClientConnect += ClientConnectHandler; } } public void RemoveRegion(Scene scene) { if (!String.IsNullOrEmpty(m_serverUrl)) { scene.EventManager.OnClientConnect -= ClientConnectHandler; } } #endregion INonSharedRegionModule public SimianProfiles(IConfigSource source) { Initialise(source); } public void Initialise(IConfigSource source) { if (Simian.IsSimianEnabled(source, "UserAccountServices", "SimianUserAccountServiceConnector")) { IConfig gridConfig = source.Configs["UserAccountService"]; if (gridConfig == null) { m_log.Error("[SIMIAN PROFILES]: UserAccountService missing from OpenSim.ini"); throw new Exception("Profiles init error"); } string serviceUrl = gridConfig.GetString("UserAccountServerURI"); if (String.IsNullOrEmpty(serviceUrl)) { m_log.Error("[SIMIAN PROFILES]: No UserAccountServerURI in section UserAccountService"); throw new Exception("Profiles init error"); } if (!serviceUrl.EndsWith("/")) serviceUrl = serviceUrl + '/'; m_serverUrl = serviceUrl; } } private void ClientConnectHandler(IClientCore clientCore) { if (clientCore is IClientAPI) { IClientAPI client = (IClientAPI)clientCore; // Classifieds client.AddGenericPacketHandler("avatarclassifiedsrequest", AvatarClassifiedsRequestHandler); client.OnClassifiedInfoRequest += ClassifiedInfoRequestHandler; client.OnClassifiedInfoUpdate += ClassifiedInfoUpdateHandler; client.OnClassifiedDelete += ClassifiedDeleteHandler; // Picks client.AddGenericPacketHandler("avatarpicksrequest", HandleAvatarPicksRequest); client.AddGenericPacketHandler("pickinforequest", HandlePickInfoRequest); client.OnPickInfoUpdate += PickInfoUpdateHandler; client.OnPickDelete += PickDeleteHandler; // Notes client.AddGenericPacketHandler("avatarnotesrequest", HandleAvatarNotesRequest); client.OnAvatarNotesUpdate += AvatarNotesUpdateHandler; // Profiles client.OnRequestAvatarProperties += RequestAvatarPropertiesHandler; client.OnUpdateAvatarProperties += UpdateAvatarPropertiesHandler; client.OnAvatarInterestUpdate += AvatarInterestUpdateHandler; client.OnUserInfoRequest += UserInfoRequestHandler; client.OnUpdateUserInfo += UpdateUserInfoHandler; } } #region Classifieds private void AvatarClassifiedsRequestHandler(Object sender, string method, List args) { if (!(sender is IClientAPI)) return; IClientAPI client = (IClientAPI)sender; UUID targetAvatarID; if (args.Count < 1 || !UUID.TryParse(args[0], out targetAvatarID)) { m_log.Error("[SIMIAN PROFILES]: Unrecognized arguments for " + method); return; } // FIXME: Query the generic key/value store for classifieds client.SendAvatarClassifiedReply(targetAvatarID, new Dictionary(0)); } private void ClassifiedInfoRequestHandler(UUID classifiedID, IClientAPI client) { // FIXME: Fetch this info client.SendClassifiedInfoReply(classifiedID, UUID.Zero, 0, Utils.DateTimeToUnixTime(DateTime.UtcNow + TimeSpan.FromDays(1)), 0, String.Empty, String.Empty, UUID.Zero, 0, UUID.Zero, String.Empty, Vector3.Zero, String.Empty, 0, 0); } private void ClassifiedInfoUpdateHandler(UUID classifiedID, uint category, string name, string description, UUID parcelID, uint parentEstate, UUID snapshotID, Vector3 globalPos, byte classifiedFlags, int price, IClientAPI client) { // FIXME: Save this info } private void ClassifiedDeleteHandler(UUID classifiedID, IClientAPI client) { // FIXME: Delete the specified classified ad } #endregion Classifieds #region Picks private void HandleAvatarPicksRequest(Object sender, string method, List args) { if (!(sender is IClientAPI)) return; IClientAPI client = (IClientAPI)sender; UUID targetAvatarID; if (args.Count < 1 || !UUID.TryParse(args[0], out targetAvatarID)) { m_log.Error("[SIMIAN PROFILES]: Unrecognized arguments for " + method); return; } // FIXME: Fetch these client.SendAvatarPicksReply(targetAvatarID, new Dictionary(0)); } private void HandlePickInfoRequest(Object sender, string method, List args) { if (!(sender is IClientAPI)) return; IClientAPI client = (IClientAPI)sender; UUID avatarID; UUID pickID; if (args.Count < 2 || !UUID.TryParse(args[0], out avatarID) || !UUID.TryParse(args[1], out pickID)) { m_log.Error("[SIMIAN PROFILES]: Unrecognized arguments for " + method); return; } // FIXME: Fetch this client.SendPickInfoReply(pickID, avatarID, false, UUID.Zero, String.Empty, String.Empty, UUID.Zero, String.Empty, String.Empty, String.Empty, Vector3.Zero, 0, false); } private void PickInfoUpdateHandler(IClientAPI client, UUID pickID, UUID creatorID, bool topPick, string name, string desc, UUID snapshotID, int sortOrder, bool enabled) { // FIXME: Save this } private void PickDeleteHandler(IClientAPI client, UUID pickID) { // FIXME: Delete } #endregion Picks #region Notes private void HandleAvatarNotesRequest(Object sender, string method, List args) { if (!(sender is IClientAPI)) return; IClientAPI client = (IClientAPI)sender; UUID targetAvatarID; if (args.Count < 1 || !UUID.TryParse(args[0], out targetAvatarID)) { m_log.Error("[SIMIAN PROFILES]: Unrecognized arguments for " + method); return; } // FIXME: Fetch this client.SendAvatarNotesReply(targetAvatarID, String.Empty); } private void AvatarNotesUpdateHandler(IClientAPI client, UUID targetID, string notes) { // FIXME: Save this } #endregion Notes #region Profiles private void RequestAvatarPropertiesHandler(IClientAPI client, UUID avatarID) { OSDMap user = FetchUserData(avatarID); ProfileFlags flags = ProfileFlags.AllowPublish | ProfileFlags.MaturePublish; if (user != null) { OSDMap about = null; if (user.ContainsKey("LLAbout")) { try { about = OSDParser.DeserializeJson(user["LLAbout"].AsString()) as OSDMap; } catch { } } if (about == null) about = new OSDMap(0); // Check if this user is a grid operator byte[] charterMember; if (user["AccessLevel"].AsInteger() >= 200) charterMember = Utils.StringToBytes("Operator"); else charterMember = Utils.EmptyBytes; // Check if the user is online if (client.Scene is Scene) { OpenSim.Services.Interfaces.PresenceInfo[] presences = ((Scene)client.Scene).PresenceService.GetAgents(new string[] { avatarID.ToString() }); if (presences != null && presences.Length > 0) flags |= ProfileFlags.Online; } // Check if the user is identified if (user["Identified"].AsBoolean()) flags |= ProfileFlags.Identified; client.SendAvatarProperties(avatarID, about["About"].AsString(), user["CreationDate"].AsDate().ToString("M/d/yyyy", System.Globalization.CultureInfo.InvariantCulture), charterMember, about["FLAbout"].AsString(), (uint)flags, about["FLImage"].AsUUID(), about["Image"].AsUUID(), about["URL"].AsString(), user["Partner"].AsUUID()); } else { m_log.Warn("[SIMIAN PROFILES]: Failed to fetch profile information for " + client.Name + ", returning default values"); client.SendAvatarProperties(avatarID, String.Empty, "1/1/1970", Utils.EmptyBytes, String.Empty, (uint)flags, UUID.Zero, UUID.Zero, String.Empty, UUID.Zero); } } private void UpdateAvatarPropertiesHandler(IClientAPI client, UserProfileData profileData) { OSDMap map = new OSDMap { { "About", OSD.FromString(profileData.AboutText) }, { "Image", OSD.FromUUID(profileData.Image) }, { "FLAbout", OSD.FromString(profileData.FirstLifeAboutText) }, { "FLImage", OSD.FromUUID(profileData.FirstLifeImage) }, { "URL", OSD.FromString(profileData.ProfileUrl) } }; AddUserData(client.AgentId, "LLAbout", map); } private void AvatarInterestUpdateHandler(IClientAPI client, uint wantmask, string wanttext, uint skillsmask, string skillstext, string languages) { OSDMap map = new OSDMap { { "WantMask", OSD.FromInteger(wantmask) }, { "WantText", OSD.FromString(wanttext) }, { "SkillsMask", OSD.FromInteger(skillsmask) }, { "SkillsText", OSD.FromString(skillstext) }, { "Languages", OSD.FromString(languages) } }; AddUserData(client.AgentId, "LLInterests", map); } private void UserInfoRequestHandler(IClientAPI client) { m_log.Error("[SIMIAN PROFILES]: UserInfoRequestHandler"); // Fetch this user's e-mail address NameValueCollection requestArgs = new NameValueCollection { { "RequestMethod", "GetUser" }, { "UserID", client.AgentId.ToString() } }; OSDMap response = WebUtil.PostToService(m_serverUrl, requestArgs); string email = response["Email"].AsString(); if (!response["Success"].AsBoolean()) m_log.Warn("[SIMIAN PROFILES]: GetUser failed during a user info request for " + client.Name); client.SendUserInfoReply(false, true, email); } private void UpdateUserInfoHandler(bool imViaEmail, bool visible, IClientAPI client) { m_log.Info("[SIMIAN PROFILES]: Ignoring user info update from " + client.Name); } #endregion Profiles /// /// Sanity checks regions for a valid estate owner at startup /// private void CheckEstateManager(Scene scene) { EstateSettings estate = scene.RegionInfo.EstateSettings; if (estate.EstateOwner == UUID.Zero) { // Attempt to lookup the grid admin UserAccount admin = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, UUID.Zero); if (admin != null) { m_log.InfoFormat("[SIMIAN PROFILES]: Setting estate {0} (ID: {1}) owner to {2}", estate.EstateName, estate.EstateID, admin.Name); estate.EstateOwner = admin.PrincipalID; estate.Save(); } else { m_log.WarnFormat("[SIMIAN PROFILES]: Estate {0} (ID: {1}) does not have an owner", estate.EstateName, estate.EstateID); } } } private bool AddUserData(UUID userID, string key, OSDMap value) { NameValueCollection requestArgs = new NameValueCollection { { "RequestMethod", "AddUserData" }, { "UserID", userID.ToString() }, { key, OSDParser.SerializeJsonString(value) } }; OSDMap response = WebUtil.PostToService(m_serverUrl, requestArgs); bool success = response["Success"].AsBoolean(); if (!success) m_log.WarnFormat("[SIMIAN PROFILES]: Failed to add user data with key {0} for {1}: {2}", key, userID, response["Message"].AsString()); return success; } private OSDMap FetchUserData(UUID userID) { NameValueCollection requestArgs = new NameValueCollection { { "RequestMethod", "GetUser" }, { "UserID", userID.ToString() } }; OSDMap response = WebUtil.PostToService(m_serverUrl, requestArgs); if (response["Success"].AsBoolean() && response["User"] is OSDMap) { return (OSDMap)response["User"]; } else { m_log.Error("[SIMIAN PROFILES]: Failed to fetch user data for " + userID + ": " + response["Message"].AsString()); } return null; } } }