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