383 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>4 * Copyright (C) 2018 Samsung Electronics Co., Ltd.5 */6 7#include <linux/kernel.h>8#include <linux/xattr.h>9#include <linux/fs.h>10#include <linux/unicode.h>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 include21 * wildcard '*' and '?'22 * TODO : implement consideration about DOS_DOT, DOS_QM and DOS_STAR23 *24 * @str: string to compare with a pattern25 * @len: string length26 * @pattern: pattern string which might include wildcard '*' and '?'27 *28 * Return: 0 if pattern matched with the string, otherwise non zero value29 */30int match_pattern(const char *str, size_t len, const char *pattern)31{32 const char *s = str;33 const char *p = pattern;34 bool star = false;35 36 while (*s && len) {37 switch (*p) {38 case '?':39 s++;40 len--;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++;53 len--;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 character73 * @ch: input character to be checked74 *75 * Return: 1 if char is allowed, otherwise 076 */77static inline int is_char_allowed(char ch)78{79 /* check for control chars, wildcards etc. */80 if (!(ch & 0x80) &&81 (ch <= 0x1f ||82 ch == '?' || ch == '"' || ch == '<' ||83 ch == '>' || ch == '|' || ch == '*'))84 return 0;85 86 return 1;87}88 89int 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 104static 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 == '\\') {111 pr_err("Stream name validation failed: %c\n", c);112 return -ENOENT;113 }114 }115 116 return 0;117}118 119int 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,139 stream_type);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 else145 rc = -ENOENT;146 }147 148 *stream_name = s_name;149out:150 return rc;151}152 153/**154 * convert_to_nt_pathname() - extract and return windows path string155 * whose share directory prefix was removed from file path156 * @share: ksmbd_share_config pointer157 * @path: path to report158 *159 * Return : windows path string or error160 */161 162char *convert_to_nt_pathname(struct ksmbd_share_config *share,163 const struct path *path)164{165 char *pathname, *ab_pathname, *nt_pathname;166 int share_path_len = share->path_sz;167 168 pathname = kmalloc(PATH_MAX, GFP_KERNEL);169 if (!pathname)170 return ERR_PTR(-EACCES);171 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);193 194free_pathname:195 kfree(pathname);196 return nt_pathname;197}198 199int 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 210void ksmbd_conv_path_to_unix(char *path)211{212 strreplace(path, '\\', '/');213}214 215void ksmbd_strip_last_slash(char *path)216{217 int len = strlen(path);218 219 while (len && path[len - 1] == '/') {220 path[len - 1] = '\0';221 len--;222 }223}224 225void ksmbd_conv_path_to_windows(char *path)226{227 strreplace(path, '/', '\\');228}229 230char *ksmbd_casefold_sharename(struct unicode_map *um, const char *name)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 250out_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 262/**263 * ksmbd_extract_sharename() - get share name from tree connect request264 * @um: pointer to a unicode_map structure for character encoding handling265 * @treename: buffer containing tree name and share name266 *267 * Return: share name on success, otherwise error268 */269char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename)270{271 const char *name = treename, *pos = strrchr(name, '\\');272 273 if (pos)274 name = (pos + 1);275 276 /* caller has to free the memory */277 return ksmbd_casefold_sharename(um, name);278}279 280/**281 * convert_to_unix_name() - convert windows name to unix format282 * @share: ksmbd_share_config pointer283 * @name: file name that is relative to share284 *285 * Return: converted name on success, otherwise NULL286 */287char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name)288{289 int no_slash = 0, name_len, path_len;290 char *new_name;291 292 if (name[0] == '/')293 name++;294 295 path_len = share->path_sz;296 name_len = strlen(name);297 new_name = kmalloc(path_len + name_len + 2, GFP_KERNEL);298 if (!new_name)299 return new_name;300 301 memcpy(new_name, share->path, path_len);302 if (new_name[path_len - 1] != '/') {303 new_name[path_len] = '/';304 no_slash = 1;305 }306 307 memcpy(new_name + path_len + no_slash, name, name_len);308 path_len += name_len + no_slash;309 new_name[path_len] = 0x00;310 return new_name;311}312 313char *ksmbd_convert_dir_info_name(struct ksmbd_dir_info *d_info,314 const struct nls_table *local_nls,315 int *conv_len)316{317 char *conv;318 int sz = min(4 * d_info->name_len, PATH_MAX);319 320 if (!sz)321 return NULL;322 323 conv = kmalloc(sz, GFP_KERNEL);324 if (!conv)325 return NULL;326 327 /* XXX */328 *conv_len = smbConvertToUTF16((__le16 *)conv, d_info->name,329 d_info->name_len, local_nls, 0);330 *conv_len *= 2;331 332 /* We allocate buffer twice bigger than needed. */333 conv[*conv_len] = 0x00;334 conv[*conv_len + 1] = 0x00;335 return conv;336}337 338/*339 * Convert the NT UTC (based 1601-01-01, in hundred nanosecond units)340 * into Unix UTC (based 1970-01-01, in seconds).341 */342struct timespec64 ksmbd_NTtimeToUnix(__le64 ntutc)343{344 struct timespec64 ts;345 346 /* Subtract the NTFS time offset, then convert to 1s intervals. */347 s64 t = le64_to_cpu(ntutc) - NTFS_TIME_OFFSET;348 u64 abs_t;349 350 /*351 * Unfortunately can not use normal 64 bit division on 32 bit arch, but352 * the alternative, do_div, does not work with negative numbers so have353 * to special case them354 */355 if (t < 0) {356 abs_t = -t;357 ts.tv_nsec = do_div(abs_t, 10000000) * 100;358 ts.tv_nsec = -ts.tv_nsec;359 ts.tv_sec = -abs_t;360 } else {361 abs_t = t;362 ts.tv_nsec = do_div(abs_t, 10000000) * 100;363 ts.tv_sec = abs_t;364 }365 366 return ts;367}368 369/* Convert the Unix UTC into NT UTC. */370inline u64 ksmbd_UnixTimeToNT(struct timespec64 t)371{372 /* Convert to 100ns intervals and then add the NTFS time offset. */373 return (u64)t.tv_sec * 10000000 + t.tv_nsec / 100 + NTFS_TIME_OFFSET;374}375 376inline long long ksmbd_systime(void)377{378 struct timespec64 ts;379 380 ktime_get_real_ts64(&ts);381 return ksmbd_UnixTimeToNT(ts);382}383