1 /*
2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <grp.h>
17 #include <pwd.h>
18 #include <sys/stat.h>
19 #include <sys/xattr.h>
20
21 #include "file_sharing/acl.h"
22 #include "securec.h"
23 #include "storage_service_log.h"
24 #include "utils/file_utils.h"
25
26 constexpr int BUF_SIZE = 400;
27
28 namespace OHOS {
29 namespace StorageDaemon {
30 namespace {
AclEntryParseTag(const std::string & tagTxt,AclXattrEntry & entry)31 int AclEntryParseTag(const std::string &tagTxt, AclXattrEntry &entry)
32 {
33 if (tagTxt.empty()) {
34 errno = EINVAL;
35 return -1;
36 }
37 switch (tagTxt[0]) {
38 case 'u':
39 entry.tag = ACL_TAG::USER;
40 break;
41 case 'g':
42 entry.tag = ACL_TAG::GROUP;
43 break;
44 case 'm':
45 entry.tag = ACL_TAG::MASK;
46 break;
47 case 'o':
48 entry.tag = ACL_TAG::OTHER;
49 break;
50 default:
51 errno = EINVAL;
52 return -1;
53 }
54 return 0;
55 }
56
ParseNumericId(const std::string & idTxt,unsigned int & outId)57 bool ParseNumericId(const std::string &idTxt, unsigned int &outId)
58 {
59 char *p = nullptr;
60 long converted = strtol(idTxt.c_str(), &p, 10);
61 if (*p == '\0' && converted >= 0 && converted <= UINT_MAX) {
62 outId = static_cast<unsigned int>(converted);
63 return true;
64 }
65 return false;
66 }
67
AclEntryParseId(const std::string & idTxt,AclXattrEntry & entry)68 int AclEntryParseId(const std::string &idTxt, AclXattrEntry &entry)
69 {
70 struct passwd *pwd = nullptr;
71 struct group *grp = nullptr;
72
73 switch (entry.tag) {
74 case ACL_TAG::USER:
75 if (idTxt.empty()) {
76 entry.tag = ACL_TAG::USER_OBJ;
77 return 0;
78 }
79 if (ParseNumericId(idTxt, entry.id)) {
80 break;
81 }
82 if ((pwd = getpwnam(idTxt.c_str())) == nullptr) {
83 return -1;
84 }
85 entry.id = pwd->pw_uid;
86 (void)memset_s(pwd, sizeof(struct passwd), 0, sizeof(struct passwd));
87 break;
88 case ACL_TAG::GROUP:
89 if (idTxt.empty()) {
90 entry.tag = ACL_TAG::GROUP_OBJ;
91 return 0;
92 }
93 if (ParseNumericId(idTxt, entry.id)) {
94 break;
95 }
96 if ((grp = getgrnam(idTxt.c_str())) == nullptr) {
97 return -1;
98 }
99 entry.id = grp->gr_gid;
100 (void)memset_s(grp, sizeof(struct group), 0, sizeof(struct group));
101 break;
102 default:
103 if (!idTxt.empty()) {
104 errno = EINVAL;
105 return -1;
106 }
107 break;
108 }
109 return 0;
110 }
111
AclEntryParsePerm(const std::string & permTxt,AclXattrEntry & entry)112 int AclEntryParsePerm(const std::string &permTxt, AclXattrEntry &entry)
113 {
114 if (permTxt.empty()) {
115 errno = EINVAL;
116 return -1;
117 }
118 for (const char &c : permTxt) {
119 switch (c) {
120 case 'r':
121 entry.perm.SetR();
122 break;
123 case 'w':
124 entry.perm.SetW();
125 break;
126 case 'x':
127 entry.perm.SetE();
128 break;
129 case '-':
130 break;
131 default:
132 errno = EINVAL;
133 return -1;
134 }
135 }
136 return 0;
137 }
138
AclEntryParseText(const std::string & entryTxt)139 AclXattrEntry AclEntryParseText(const std::string &entryTxt)
140 {
141 AclXattrEntry entry = {};
142 std::string::size_type last = 0;
143 std::string::size_type pos;
144
145 if ((pos = entryTxt.find(":", last)) == std::string::npos) {
146 LOGE("Invalid ACL entry format");
147 return {};
148 }
149 const std::string tagTxt = entryTxt.substr(last, pos - last);
150 if (AclEntryParseTag(tagTxt, entry) == -1) {
151 LOGE("Unknown tag: %{public}s", tagTxt.c_str());
152 return {};
153 }
154 last = pos + 1;
155
156 if ((pos = entryTxt.find(":", last)) == std::string::npos) {
157 LOGE("Invalid ACL entry format");
158 return {};
159 }
160 const std::string idTxt = entryTxt.substr(last, pos - last);
161 if (AclEntryParseId(idTxt, entry) == -1) {
162 switch (entry.tag) {
163 case ACL_TAG::USER:
164 case ACL_TAG::GROUP:
165 LOGE("Error in processing qualifier: \"%{public}s\": %{public}s",
166 idTxt.c_str(),
167 errno == 0 ? "user/group not found" : std::strerror(errno));
168 break;
169 default:
170 LOGE("Qualifier only allowed for USER & GROUP");
171 break;
172 }
173 return {};
174 }
175 last = pos + 1;
176
177 const std::string permTxt = entryTxt.substr(last); // take substr till the end
178 if (AclEntryParsePerm(permTxt, entry) == -1) {
179 LOGE("Wrong permission: %{public}s", permTxt.c_str());
180 return {};
181 }
182
183 return entry;
184 }
185
AclFromMode(const std::string & file)186 Acl AclFromMode(const std::string &file)
187 {
188 Acl acl;
189 struct stat st;
190
191 if (stat(file.c_str(), &st) == -1) {
192 return acl;
193 }
194
195 acl.InsertEntry(
196 { .tag = ACL_TAG::USER_OBJ,
197 .perm = (st.st_mode & S_IRWXU) >> 6,
198 .id = ACL_UNDEFINED_ID, }
199 );
200 acl.InsertEntry(
201 { .tag = ACL_TAG::GROUP_OBJ,
202 .perm = (st.st_mode & S_IRWXG) >> 3,
203 .id = ACL_UNDEFINED_ID, }
204 );
205 acl.InsertEntry(
206 { .tag = ACL_TAG::OTHER,
207 .perm = (st.st_mode & S_IRWXO),
208 .id = ACL_UNDEFINED_ID, }
209 );
210
211 return acl;
212 }
213
AclFromFile(const std::string & file)214 Acl AclFromFile(const std::string &file)
215 {
216 Acl acl;
217 char buf[BUF_SIZE] = { 0 };
218 ssize_t len = getxattr(file.c_str(), ACL_XATTR_ACCESS, buf, BUF_SIZE);
219 if (len != -1) {
220 acl.DeSerialize(buf, BUF_SIZE);
221 return acl;
222 }
223 return AclFromMode(file);
224 }
225
226 } // anonymous namespace
227
AclSetAttribution(const std::string & targetFile,const std::string & entryTxt,const char * aclAttrName)228 int AclSetAttribution(const std::string &targetFile, const std::string &entryTxt, const char *aclAttrName)
229 {
230 if (strcmp(aclAttrName, ACL_XATTR_ACCESS) && !IsDir(targetFile)) {
231 LOGE("Failed to confirm is a directory: %{public}s",
232 errno == 0 ? "file exists but isn't a directory" : std::strerror(errno));
233 return -1;
234 }
235
236 /* parse text */
237 AclXattrEntry entry = AclEntryParseText(entryTxt);
238 if (!entry.IsValid()) {
239 LOGE("Failed to parse entry text: %{public}s", std::strerror(errno));
240 return -1;
241 }
242
243 /* init acl from file's mode */
244 Acl acl;
245 if (strcmp(aclAttrName, ACL_XATTR_ACCESS) == 0) {
246 acl = AclFromFile(targetFile);
247 } else {
248 acl = AclFromMode(targetFile);
249 }
250 if (acl.IsEmpty()) {
251 LOGE("Failed to generate ACL from file's mode: %{public}s", std::strerror(errno));
252 return -1;
253 }
254
255 /* add new entry into set */
256 if (acl.InsertEntry(entry) == -1) {
257 LOGE("Failed to insert new entry into ACL: %{public}s", std::strerror(errno));
258 return -1;
259 }
260
261 /* transform to binary and write to file */
262 size_t bufSize;
263 char *buf = acl.Serialize(bufSize);
264 if (buf == nullptr) {
265 LOGE("Failed to serialize ACL into binary: %{public}s, bufSize: %{public}zu",
266 std::strerror(errno), bufSize);
267 return -1;
268 }
269 if (setxattr(targetFile.c_str(), aclAttrName, buf, bufSize, 0) == -1) {
270 LOGE("Failed to write into file's xattr: %{public}s", std::strerror(errno));
271 return -1;
272 }
273 return 0;
274 }
275
AclSetDefault(const std::string & targetFile,const std::string & entryTxt)276 int AclSetDefault(const std::string &targetFile, const std::string &entryTxt)
277 {
278 return AclSetAttribution(targetFile, entryTxt, ACL_XATTR_DEFAULT);
279 }
280
AclSetAccess(const std::string & targetFile,const std::string & entryTxt)281 int AclSetAccess(const std::string &targetFile, const std::string &entryTxt)
282 {
283 return AclSetAttribution(targetFile, entryTxt, ACL_XATTR_ACCESS);
284 }
285 } // namespace StorageDaemon
286 } // namespace OHOS
287