1 /***
2     This file is part of snapcast
3     Copyright (C) 2014-2021  Johannes Pohl
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 ***/
18 
19 #include "config.hpp"
20 #include "common/aixlog.hpp"
21 #include "common/snap_exception.hpp"
22 #include "common/str_compat.hpp"
23 #include "common/utils/file_utils.hpp"
24 #include <cerrno>
25 #include <fcntl.h>
26 #include <fstream>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 
30 using namespace std;
31 
32 
33 
~Config()34 Config::~Config()
35 {
36     save();
37 }
38 
39 
init(const std::string & root_directory,const std::string & user,const std::string & group)40 void Config::init(const std::string& root_directory, const std::string& user, const std::string& group)
41 {
42     string dir;
43     if (!root_directory.empty())
44         dir = root_directory;
45     else if (getenv("HOME") == nullptr)
46         dir = "/var/lib/snapserver/";
47     else
48         dir = string(getenv("HOME")) + "/.config/snapserver/";
49 
50     if (!dir.empty() && (dir.back() != '/'))
51         dir += "/";
52 
53     // if (dir.find("/var/lib/snapserver") == string::npos)
54     //     dir += ".config/snapserver/";
55 
56     int status = utils::file::mkdirRecursive(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
57     if ((status != 0) && (errno != EEXIST))
58         throw SnapException("failed to create settings directory: \"" + dir + "\": " + cpt::to_string(errno));
59 
60     filename_ = dir + "server.json";
61     LOG(NOTICE) << "Settings file: \"" << filename_ << "\"\n";
62 
63     int fd;
64     if ((fd = open(filename_.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
65     {
66         if (errno == EACCES)
67             throw std::runtime_error("failed to open file \"" + filename_ + "\", permission denied (error " + cpt::to_string(errno) + ")");
68         else
69             throw std::runtime_error("failed to open file \"" + filename_ + "\", error " + cpt::to_string(errno));
70     }
71     close(fd);
72 
73     if (!user.empty() && !group.empty())
74     {
75         try
76         {
77             utils::file::do_chown(dir, user, group);
78             utils::file::do_chown(filename_, user, group);
79         }
80         catch (const std::exception& e)
81         {
82             LOG(ERROR) << "Exception in chown: " << e.what() << "\n";
83         }
84     }
85 
86     try
87     {
88         ifstream ifs(filename_, std::ifstream::in);
89         if (ifs.good() && (ifs.peek() != std::ifstream::traits_type::eof()))
90         {
91             json j;
92             ifs >> j;
93             if (j.count("ConfigVersion") != 0u)
94             {
95                 json jGroups = j["Groups"];
96                 for (auto& jGroup : jGroups)
97                 {
98                     GroupPtr group = make_shared<Group>();
99                     group->fromJson(jGroup);
100                     // if (client->id.empty() || getClientInfo(client->id))
101                     //     continue;
102                     groups.push_back(group);
103                 }
104             }
105         }
106     }
107     catch (const std::exception& e)
108     {
109         LOG(ERROR) << "Error reading config: " << e.what() << "\n";
110     }
111 }
112 
113 
save()114 void Config::save()
115 {
116     if (filename_.empty())
117         init();
118     std::ofstream ofs(filename_.c_str(), std::ofstream::out | std::ofstream::trunc);
119     json clients = {{"ConfigVersion", 2}, {"Groups", getGroups()}};
120     // ofs << std::setw(4) << clients;
121     ofs << clients;
122     ofs.close();
123 }
124 
125 
getClientInfo(const std::string & clientId) const126 ClientInfoPtr Config::getClientInfo(const std::string& clientId) const
127 {
128     if (clientId.empty())
129         return nullptr;
130 
131     for (const auto& group : groups)
132     {
133         for (auto client : group->clients)
134         {
135             if (client->id == clientId)
136                 return client;
137         }
138     }
139 
140     return nullptr;
141 }
142 
143 
addClientInfo(ClientInfoPtr client)144 GroupPtr Config::addClientInfo(ClientInfoPtr client)
145 {
146     GroupPtr group = getGroupFromClient(client);
147     if (!group)
148     {
149         group = std::make_shared<Group>();
150         group->addClient(client);
151         groups.push_back(group);
152     }
153     return group;
154 }
155 
156 
addClientInfo(const std::string & clientId)157 GroupPtr Config::addClientInfo(const std::string& clientId)
158 {
159     ClientInfoPtr client = getClientInfo(clientId);
160     if (!client)
161         client = make_shared<ClientInfo>(clientId);
162     return addClientInfo(client);
163 }
164 
165 
getGroup(const std::string & groupId) const166 GroupPtr Config::getGroup(const std::string& groupId) const
167 {
168     for (auto group : groups)
169     {
170         if (group->id == groupId)
171             return group;
172     }
173 
174     return nullptr;
175 }
176 
177 
getGroupFromClient(const std::string & clientId)178 GroupPtr Config::getGroupFromClient(const std::string& clientId)
179 {
180     for (auto group : groups)
181     {
182         for (const auto& c : group->clients)
183         {
184             if (c->id == clientId)
185                 return group;
186         }
187     }
188     return nullptr;
189 }
190 
191 
getGroupFromClient(ClientInfoPtr client)192 GroupPtr Config::getGroupFromClient(ClientInfoPtr client)
193 {
194     return getGroupFromClient(client->id);
195 }
196 
197 
getServerStatus(const json & streams) const198 json Config::getServerStatus(const json& streams) const
199 {
200     Host host;
201     host.update();
202     // TODO: Set MAC and IP
203     Snapserver snapserver("Snapserver", VERSION);
204     json serverStatus = {{"server",
205                           {{"host", host.toJson()}, // getHostName()},
206                            {"snapserver", snapserver.toJson()}}},
207                          {"groups", getGroups()},
208                          {"streams", streams}};
209 
210     return serverStatus;
211 }
212 
213 
214 
getGroups() const215 json Config::getGroups() const
216 {
217     json result = json::array();
218     for (const auto& group : groups)
219         result.push_back(group->toJson());
220     return result;
221 }
222 
223 
remove(ClientInfoPtr client)224 void Config::remove(ClientInfoPtr client)
225 {
226     auto group = getGroupFromClient(client);
227     if (!group)
228         return;
229     group->removeClient(client);
230     if (group->empty())
231         remove(group);
232 }
233 
234 
remove(GroupPtr group,bool force)235 void Config::remove(GroupPtr group, bool force)
236 {
237     if (!group)
238         return;
239 
240     if (group->empty() || force)
241         groups.erase(std::remove(groups.begin(), groups.end(), group), groups.end());
242 }
243 
244 /*
245 GroupPtr Config::removeFromGroup(const std::string& groupId, const std::string& clientId)
246 {
247         GroupPtr group = getGroup(groupId);
248         if (!group || (group->id != groupId))
249                 return group;
250 
251         auto client = getClientInfo(clientId);
252         if (client)
253                 group->clients.erase(std::remove(group->clients.begin(), group->clients.end(), client), group->clients.end());
254 
255         addClientInfo(clientId);
256         return group;
257 }
258 
259 
260 GroupPtr Config::setGroupForClient(const std::string& groupId, const std::string& clientId)
261 {
262         GroupPtr oldGroup = getGroupFromClient(clientId);
263         if (oldGroup && (oldGroup->id == groupId))
264                 return oldGroup;
265 
266         GroupPtr newGroup = getGroup(groupId);
267         if (!newGroup)
268                 return nullptr;
269 
270         auto client = getClientInfo(clientId);
271         if (!client)
272                 return nullptr;
273 
274         if (oldGroup)
275                 removeFromGroup(oldGroup->id, clientId);
276 
277         newGroup->addClient(client);
278         return newGroup;
279 }
280 */
281