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