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