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