1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * 4 * Copyright (c) International Business Machines Corp., 2002,2008 5 * Author(s): Steve French (sfrench@us.ibm.com) 6 * 7 * Error mapping routines from Samba libsmb/errormap.c 8 * Copyright (C) Andrew Tridgell 2001 9 */ 10 11 #include <linux/net.h> 12 #include <linux/string.h> 13 #include <linux/in.h> 14 #include <linux/ctype.h> 15 #include <linux/fs.h> 16 #include <asm/div64.h> 17 #include <asm/byteorder.h> 18 #include <linux/inet.h> 19 #include "cifsfs.h" 20 #include "cifsglob.h" 21 #include "cifsproto.h" 22 #include "smb1proto.h" 23 #include "smberr.h" 24 #include "cifs_debug.h" 25 #include "nterr.h" 26 27 /* 28 * Convert a string containing text IPv4 or IPv6 address to binary form. 29 * 30 * Returns 0 on failure. 31 */ 32 static int 33 cifs_inet_pton(const int address_family, const char *cp, int len, void *dst) 34 { 35 int ret = 0; 36 37 /* calculate length by finding first slash or NULL */ 38 if (address_family == AF_INET) 39 ret = in4_pton(cp, len, dst, '\\', NULL); 40 else if (address_family == AF_INET6) 41 ret = in6_pton(cp, len, dst , '\\', NULL); 42 43 cifs_dbg(NOISY, "address conversion returned %d for %*.*s\n", 44 ret, len, len, cp); 45 if (ret > 0) 46 ret = 1; 47 return ret; 48 } 49 50 /* 51 * Try to convert a string to an IPv4 address and then attempt to convert 52 * it to an IPv6 address if that fails. Set the family field if either 53 * succeeds. If it's an IPv6 address and it has a '%' sign in it, try to 54 * treat the part following it as a numeric sin6_scope_id. 55 * 56 * Returns 0 on failure. 57 */ 58 int 59 cifs_convert_address(struct sockaddr *dst, const char *src, int len) 60 { 61 int rc, alen, slen; 62 const char *pct; 63 char scope_id[13]; 64 struct sockaddr_in *s4 = (struct sockaddr_in *) dst; 65 struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) dst; 66 67 /* IPv4 address */ 68 if (cifs_inet_pton(AF_INET, src, len, &s4->sin_addr.s_addr)) { 69 s4->sin_family = AF_INET; 70 return 1; 71 } 72 73 /* attempt to exclude the scope ID from the address part */ 74 pct = memchr(src, '%', len); 75 alen = pct ? pct - src : len; 76 77 rc = cifs_inet_pton(AF_INET6, src, alen, &s6->sin6_addr.s6_addr); 78 if (!rc) 79 return rc; 80 81 s6->sin6_family = AF_INET6; 82 if (pct) { 83 /* grab the scope ID */ 84 slen = len - (alen + 1); 85 if (slen <= 0 || slen > 12) 86 return 0; 87 memcpy(scope_id, pct + 1, slen); 88 scope_id[slen] = '\0'; 89 90 rc = kstrtouint(scope_id, 0, &s6->sin6_scope_id); 91 rc = (rc == 0) ? 1 : 0; 92 } 93 94 return rc; 95 } 96 97 void 98 cifs_set_port(struct sockaddr *addr, const unsigned short int port) 99 { 100 switch (addr->sa_family) { 101 case AF_INET: 102 ((struct sockaddr_in *)addr)->sin_port = htons(port); 103 break; 104 case AF_INET6: 105 ((struct sockaddr_in6 *)addr)->sin6_port = htons(port); 106 break; 107 } 108 } 109 110 /* The following are taken from fs/ntfs/util.c */ 111 112 #define NTFS_TIME_OFFSET ((u64)(369*365 + 89) * 24 * 3600 * 10000000) 113 114 /* 115 * Convert the NT UTC (based 1601-01-01, in hundred nanosecond units) 116 * into Unix UTC (based 1970-01-01, in seconds). 117 */ 118 struct timespec64 119 cifs_NTtimeToUnix(__le64 ntutc) 120 { 121 struct timespec64 ts; 122 /* BB what about the timezone? BB */ 123 124 /* Subtract the NTFS time offset, then convert to 1s intervals. */ 125 s64 t = le64_to_cpu(ntutc) - NTFS_TIME_OFFSET; 126 u64 abs_t; 127 128 /* 129 * Unfortunately can not use normal 64 bit division on 32 bit arch, but 130 * the alternative, do_div, does not work with negative numbers so have 131 * to special case them 132 */ 133 if (t < 0) { 134 abs_t = -t; 135 ts.tv_nsec = (time64_t)(do_div(abs_t, 10000000) * 100); 136 ts.tv_nsec = -ts.tv_nsec; 137 ts.tv_sec = -abs_t; 138 } else { 139 abs_t = t; 140 ts.tv_nsec = (time64_t)do_div(abs_t, 10000000) * 100; 141 ts.tv_sec = abs_t; 142 } 143 144 return ts; 145 } 146 147 /* Convert the Unix UTC into NT UTC. */ 148 u64 149 cifs_UnixTimeToNT(struct timespec64 t) 150 { 151 /* Convert to 100ns intervals and then add the NTFS time offset. */ 152 return (u64) t.tv_sec * 10000000 + t.tv_nsec/100 + NTFS_TIME_OFFSET; 153 } 154 155 static const int total_days_of_prev_months[] = { 156 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 157 }; 158 159 struct timespec64 cnvrtDosUnixTm(__le16 le_date, __le16 le_time, int offset) 160 { 161 struct timespec64 ts; 162 time64_t sec, days; 163 int min, day, month, year; 164 u16 date = le16_to_cpu(le_date); 165 u16 time = le16_to_cpu(le_time); 166 SMB_TIME *st = (SMB_TIME *)&time; 167 SMB_DATE *sd = (SMB_DATE *)&date; 168 169 cifs_dbg(FYI, "date %d time %d\n", date, time); 170 171 sec = 2 * st->TwoSeconds; 172 min = st->Minutes; 173 if ((sec > 59) || (min > 59)) 174 cifs_dbg(VFS, "Invalid time min %d sec %lld\n", min, sec); 175 sec += (min * 60); 176 sec += 60 * 60 * st->Hours; 177 if (st->Hours > 24) 178 cifs_dbg(VFS, "Invalid hours %d\n", st->Hours); 179 day = sd->Day; 180 month = sd->Month; 181 if (day < 1 || day > 31 || month < 1 || month > 12) { 182 cifs_dbg(VFS, "Invalid date, month %d day: %d\n", month, day); 183 day = clamp(day, 1, 31); 184 month = clamp(month, 1, 12); 185 } 186 month -= 1; 187 days = day + total_days_of_prev_months[month]; 188 days += 3652; /* account for difference in days between 1980 and 1970 */ 189 year = sd->Year; 190 days += year * 365; 191 days += (year/4); /* leap year */ 192 /* generalized leap year calculation is more complex, ie no leap year 193 for years/100 except for years/400, but since the maximum number for DOS 194 year is 2**7, the last year is 1980+127, which means we need only 195 consider 2 special case years, ie the years 2000 and 2100, and only 196 adjust for the lack of leap year for the year 2100, as 2000 was a 197 leap year (divisible by 400) */ 198 if (year >= 120) /* the year 2100 */ 199 days = days - 1; /* do not count leap year for the year 2100 */ 200 201 /* adjust for leap year where we are still before leap day */ 202 if (year != 120) 203 days -= ((year & 0x03) == 0) && (month < 2 ? 1 : 0); 204 sec += 24 * 60 * 60 * days; 205 206 ts.tv_sec = sec + offset; 207 208 /* cifs_dbg(FYI, "sec after cnvrt dos to unix time %d\n",sec); */ 209 210 ts.tv_nsec = 0; 211 return ts; 212 } 213