Commit | Line | Data |
---|---|---|
e2f34481 NJ |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* | |
3 | * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org> | |
4 | * Copyright (C) 2018 Samsung Electronics Co., Ltd. | |
5 | */ | |
6 | ||
7 | #include <linux/kernel.h> | |
e2f34481 NJ |
8 | #include <linux/xattr.h> |
9 | #include <linux/fs.h> | |
16b5f54e | 10 | #include <linux/unicode.h> |
e2f34481 NJ |
11 | |
12 | #include "misc.h" | |
13 | #include "smb_common.h" | |
14 | #include "connection.h" | |
15 | #include "vfs.h" | |
16 | ||
17 | #include "mgmt/share_config.h" | |
18 | ||
19 | /** | |
20 | * match_pattern() - compare a string with a pattern which might include | |
21 | * wildcard '*' and '?' | |
22 | * TODO : implement consideration about DOS_DOT, DOS_QM and DOS_STAR | |
23 | * | |
7820c6ee | 24 | * @str: string to compare with a pattern |
b24c9335 | 25 | * @len: string length |
e2f34481 NJ |
26 | * @pattern: pattern string which might include wildcard '*' and '?' |
27 | * | |
28 | * Return: 0 if pattern matched with the string, otherwise non zero value | |
29 | */ | |
b24c9335 | 30 | int match_pattern(const char *str, size_t len, const char *pattern) |
e2f34481 NJ |
31 | { |
32 | const char *s = str; | |
33 | const char *p = pattern; | |
34 | bool star = false; | |
35 | ||
b24c9335 | 36 | while (*s && len) { |
e2f34481 NJ |
37 | switch (*p) { |
38 | case '?': | |
39 | s++; | |
b24c9335 | 40 | len--; |
e2f34481 NJ |
41 | p++; |
42 | break; | |
43 | case '*': | |
44 | star = true; | |
45 | str = s; | |
46 | if (!*++p) | |
47 | return true; | |
48 | pattern = p; | |
49 | break; | |
50 | default: | |
51 | if (tolower(*s) == tolower(*p)) { | |
52 | s++; | |
b24c9335 | 53 | len--; |
e2f34481 NJ |
54 | p++; |
55 | } else { | |
56 | if (!star) | |
57 | return false; | |
58 | str++; | |
59 | s = str; | |
60 | p = pattern; | |
61 | } | |
62 | break; | |
63 | } | |
64 | } | |
65 | ||
66 | if (*p == '*') | |
67 | ++p; | |
68 | return !*p; | |
69 | } | |
70 | ||
71 | /* | |
72 | * is_char_allowed() - check for valid character | |
73 | * @ch: input character to be checked | |
74 | * | |
75 | * Return: 1 if char is allowed, otherwise 0 | |
76 | */ | |
77 | static inline int is_char_allowed(char ch) | |
78 | { | |
79 | /* check for control chars, wildcards etc. */ | |
80 | if (!(ch & 0x80) && | |
64b39f4a NJ |
81 | (ch <= 0x1f || |
82 | ch == '?' || ch == '"' || ch == '<' || | |
83 | ch == '>' || ch == '|' || ch == '*')) | |
e2f34481 NJ |
84 | return 0; |
85 | ||
86 | return 1; | |
87 | } | |
88 | ||
89 | int ksmbd_validate_filename(char *filename) | |
90 | { | |
91 | while (*filename) { | |
92 | char c = *filename; | |
93 | ||
94 | filename++; | |
95 | if (!is_char_allowed(c)) { | |
96 | ksmbd_debug(VFS, "File name validation failed: 0x%x\n", c); | |
97 | return -ENOENT; | |
98 | } | |
99 | } | |
100 | ||
101 | return 0; | |
102 | } | |
103 | ||
104 | static int ksmbd_validate_stream_name(char *stream_name) | |
105 | { | |
106 | while (*stream_name) { | |
107 | char c = *stream_name; | |
108 | ||
109 | stream_name++; | |
110 | if (c == '/' || c == ':' || c == '\\') { | |
bde1694a | 111 | pr_err("Stream name validation failed: %c\n", c); |
e2f34481 NJ |
112 | return -ENOENT; |
113 | } | |
114 | } | |
115 | ||
116 | return 0; | |
117 | } | |
118 | ||
119 | int parse_stream_name(char *filename, char **stream_name, int *s_type) | |
120 | { | |
121 | char *stream_type; | |
122 | char *s_name; | |
123 | int rc = 0; | |
124 | ||
125 | s_name = filename; | |
126 | filename = strsep(&s_name, ":"); | |
127 | ksmbd_debug(SMB, "filename : %s, streams : %s\n", filename, s_name); | |
128 | if (strchr(s_name, ':')) { | |
129 | stream_type = s_name; | |
130 | s_name = strsep(&stream_type, ":"); | |
131 | ||
132 | rc = ksmbd_validate_stream_name(s_name); | |
133 | if (rc < 0) { | |
134 | rc = -ENOENT; | |
135 | goto out; | |
136 | } | |
137 | ||
138 | ksmbd_debug(SMB, "stream name : %s, stream type : %s\n", s_name, | |
070fb21e | 139 | stream_type); |
e2f34481 NJ |
140 | if (!strncasecmp("$data", stream_type, 5)) |
141 | *s_type = DATA_STREAM; | |
142 | else if (!strncasecmp("$index_allocation", stream_type, 17)) | |
143 | *s_type = DIR_STREAM; | |
144 | else | |
145 | rc = -ENOENT; | |
146 | } | |
147 | ||
148 | *stream_name = s_name; | |
149 | out: | |
150 | return rc; | |
151 | } | |
152 | ||
153 | /** | |
154 | * convert_to_nt_pathname() - extract and return windows path string | |
155 | * whose share directory prefix was removed from file path | |
7820c6ee YL |
156 | * @share: ksmbd_share_config pointer |
157 | * @path: path to report | |
e2f34481 NJ |
158 | * |
159 | * Return : windows path string or error | |
160 | */ | |
161 | ||
50f500b7 | 162 | char *convert_to_nt_pathname(struct ksmbd_share_config *share, |
c22180a5 | 163 | const struct path *path) |
e2f34481 | 164 | { |
50f500b7 NJ |
165 | char *pathname, *ab_pathname, *nt_pathname; |
166 | int share_path_len = share->path_sz; | |
e2f34481 | 167 | |
50f500b7 NJ |
168 | pathname = kmalloc(PATH_MAX, GFP_KERNEL); |
169 | if (!pathname) | |
170 | return ERR_PTR(-EACCES); | |
87ffb310 | 171 | |
50f500b7 NJ |
172 | ab_pathname = d_path(path, pathname, PATH_MAX); |
173 | if (IS_ERR(ab_pathname)) { | |
174 | nt_pathname = ERR_PTR(-EACCES); | |
175 | goto free_pathname; | |
176 | } | |
177 | ||
178 | if (strncmp(ab_pathname, share->path, share_path_len)) { | |
179 | nt_pathname = ERR_PTR(-EACCES); | |
180 | goto free_pathname; | |
181 | } | |
182 | ||
183 | nt_pathname = kzalloc(strlen(&ab_pathname[share_path_len]) + 2, GFP_KERNEL); | |
184 | if (!nt_pathname) { | |
185 | nt_pathname = ERR_PTR(-ENOMEM); | |
186 | goto free_pathname; | |
187 | } | |
188 | if (ab_pathname[share_path_len] == '\0') | |
189 | strcpy(nt_pathname, "/"); | |
190 | strcat(nt_pathname, &ab_pathname[share_path_len]); | |
191 | ||
192 | ksmbd_conv_path_to_windows(nt_pathname); | |
87ffb310 | 193 | |
50f500b7 NJ |
194 | free_pathname: |
195 | kfree(pathname); | |
196 | return nt_pathname; | |
e2f34481 NJ |
197 | } |
198 | ||
199 | int get_nlink(struct kstat *st) | |
200 | { | |
201 | int nlink; | |
202 | ||
203 | nlink = st->nlink; | |
204 | if (S_ISDIR(st->mode)) | |
205 | nlink--; | |
206 | ||
207 | return nlink; | |
208 | } | |
209 | ||
265fd199 | 210 | void ksmbd_conv_path_to_unix(char *path) |
e2f34481 NJ |
211 | { |
212 | strreplace(path, '\\', '/'); | |
265fd199 | 213 | } |
f58eae6c | 214 | |
265fd199 HL |
215 | void ksmbd_strip_last_slash(char *path) |
216 | { | |
217 | int len = strlen(path); | |
f58eae6c | 218 | |
265fd199 HL |
219 | while (len && path[len - 1] == '/') { |
220 | path[len - 1] = '\0'; | |
221 | len--; | |
222 | } | |
e2f34481 NJ |
223 | } |
224 | ||
225 | void ksmbd_conv_path_to_windows(char *path) | |
226 | { | |
227 | strreplace(path, '/', '\\'); | |
228 | } | |
229 | ||
f5ba1cda | 230 | char *ksmbd_casefold_sharename(struct unicode_map *um, const char *name) |
16b5f54e AH |
231 | { |
232 | char *cf_name; | |
233 | int cf_len; | |
234 | ||
235 | cf_name = kzalloc(KSMBD_REQ_MAX_SHARE_NAME, GFP_KERNEL); | |
236 | if (!cf_name) | |
237 | return ERR_PTR(-ENOMEM); | |
238 | ||
239 | if (IS_ENABLED(CONFIG_UNICODE) && um) { | |
240 | const struct qstr q_name = {.name = name, .len = strlen(name)}; | |
241 | ||
242 | cf_len = utf8_casefold(um, &q_name, cf_name, | |
243 | KSMBD_REQ_MAX_SHARE_NAME); | |
244 | if (cf_len < 0) | |
245 | goto out_ascii; | |
246 | ||
247 | return cf_name; | |
248 | } | |
249 | ||
250 | out_ascii: | |
251 | cf_len = strscpy(cf_name, name, KSMBD_REQ_MAX_SHARE_NAME); | |
252 | if (cf_len < 0) { | |
253 | kfree(cf_name); | |
254 | return ERR_PTR(-E2BIG); | |
255 | } | |
256 | ||
257 | for (; *cf_name; ++cf_name) | |
258 | *cf_name = isascii(*cf_name) ? tolower(*cf_name) : *cf_name; | |
259 | return cf_name - cf_len; | |
260 | } | |
261 | ||
e2f34481 | 262 | /** |
36ba3866 | 263 | * ksmbd_extract_sharename() - get share name from tree connect request |
e2f34481 NJ |
264 | * @treename: buffer containing tree name and share name |
265 | * | |
266 | * Return: share name on success, otherwise error | |
267 | */ | |
16b5f54e | 268 | char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename) |
e2f34481 | 269 | { |
16b5f54e | 270 | const char *name = treename, *pos = strrchr(name, '\\'); |
e2f34481 NJ |
271 | |
272 | if (pos) | |
273 | name = (pos + 1); | |
274 | ||
275 | /* caller has to free the memory */ | |
f5ba1cda | 276 | return ksmbd_casefold_sharename(um, name); |
e2f34481 NJ |
277 | } |
278 | ||
279 | /** | |
280 | * convert_to_unix_name() - convert windows name to unix format | |
7820c6ee YL |
281 | * @share: ksmbd_share_config pointer |
282 | * @name: file name that is relative to share | |
e2f34481 NJ |
283 | * |
284 | * Return: converted name on success, otherwise NULL | |
285 | */ | |
265fd199 | 286 | char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name) |
e2f34481 NJ |
287 | { |
288 | int no_slash = 0, name_len, path_len; | |
289 | char *new_name; | |
290 | ||
291 | if (name[0] == '/') | |
292 | name++; | |
293 | ||
294 | path_len = share->path_sz; | |
295 | name_len = strlen(name); | |
296 | new_name = kmalloc(path_len + name_len + 2, GFP_KERNEL); | |
297 | if (!new_name) | |
298 | return new_name; | |
299 | ||
300 | memcpy(new_name, share->path, path_len); | |
301 | if (new_name[path_len - 1] != '/') { | |
302 | new_name[path_len] = '/'; | |
303 | no_slash = 1; | |
304 | } | |
305 | ||
306 | memcpy(new_name + path_len + no_slash, name, name_len); | |
307 | path_len += name_len + no_slash; | |
308 | new_name[path_len] = 0x00; | |
309 | return new_name; | |
310 | } | |
311 | ||
312 | char *ksmbd_convert_dir_info_name(struct ksmbd_dir_info *d_info, | |
070fb21e NJ |
313 | const struct nls_table *local_nls, |
314 | int *conv_len) | |
e2f34481 NJ |
315 | { |
316 | char *conv; | |
317 | int sz = min(4 * d_info->name_len, PATH_MAX); | |
318 | ||
319 | if (!sz) | |
320 | return NULL; | |
321 | ||
322 | conv = kmalloc(sz, GFP_KERNEL); | |
323 | if (!conv) | |
324 | return NULL; | |
325 | ||
326 | /* XXX */ | |
070fb21e NJ |
327 | *conv_len = smbConvertToUTF16((__le16 *)conv, d_info->name, |
328 | d_info->name_len, local_nls, 0); | |
e2f34481 NJ |
329 | *conv_len *= 2; |
330 | ||
331 | /* We allocate buffer twice bigger than needed. */ | |
332 | conv[*conv_len] = 0x00; | |
333 | conv[*conv_len + 1] = 0x00; | |
334 | return conv; | |
335 | } | |
5626518e NJ |
336 | |
337 | /* | |
338 | * Convert the NT UTC (based 1601-01-01, in hundred nanosecond units) | |
339 | * into Unix UTC (based 1970-01-01, in seconds). | |
340 | */ | |
341 | struct timespec64 ksmbd_NTtimeToUnix(__le64 ntutc) | |
342 | { | |
343 | struct timespec64 ts; | |
344 | ||
345 | /* Subtract the NTFS time offset, then convert to 1s intervals. */ | |
346 | s64 t = le64_to_cpu(ntutc) - NTFS_TIME_OFFSET; | |
347 | u64 abs_t; | |
348 | ||
349 | /* | |
350 | * Unfortunately can not use normal 64 bit division on 32 bit arch, but | |
351 | * the alternative, do_div, does not work with negative numbers so have | |
352 | * to special case them | |
353 | */ | |
354 | if (t < 0) { | |
355 | abs_t = -t; | |
356 | ts.tv_nsec = do_div(abs_t, 10000000) * 100; | |
357 | ts.tv_nsec = -ts.tv_nsec; | |
358 | ts.tv_sec = -abs_t; | |
359 | } else { | |
360 | abs_t = t; | |
361 | ts.tv_nsec = do_div(abs_t, 10000000) * 100; | |
362 | ts.tv_sec = abs_t; | |
363 | } | |
364 | ||
365 | return ts; | |
366 | } | |
367 | ||
368 | /* Convert the Unix UTC into NT UTC. */ | |
369 | inline u64 ksmbd_UnixTimeToNT(struct timespec64 t) | |
370 | { | |
371 | /* Convert to 100ns intervals and then add the NTFS time offset. */ | |
372 | return (u64)t.tv_sec * 10000000 + t.tv_nsec / 100 + NTFS_TIME_OFFSET; | |
373 | } | |
374 | ||
375 | inline long long ksmbd_systime(void) | |
376 | { | |
377 | struct timespec64 ts; | |
378 | ||
379 | ktime_get_real_ts64(&ts); | |
380 | return ksmbd_UnixTimeToNT(ts); | |
381 | } |