1 /* $OpenBSD: util.c,v 1.20 2009/04/24 10:02:35 jacekm Exp $ */ 2 3 /* 4 * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> 5 * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/types.h> 21 #include <sys/param.h> 22 #include <sys/queue.h> 23 #include <sys/tree.h> 24 #include <sys/socket.h> 25 #include <sys/stat.h> 26 27 #include <ctype.h> 28 #include <errno.h> 29 #include <event.h> 30 #include <libgen.h> 31 #include <netdb.h> 32 #include <pwd.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <time.h> 37 #include <unistd.h> 38 39 #include "smtpd.h" 40 41 int 42 bsnprintf(char *str, size_t size, const char *format, ...) 43 { 44 int ret; 45 va_list ap; 46 47 va_start(ap, format); 48 ret = vsnprintf(str, size, format, ap); 49 va_end(ap); 50 if (ret == -1 || ret >= (int)size) 51 return 0; 52 53 return 1; 54 } 55 56 /* Close file, signifying temporary error condition (if any) to the caller. */ 57 int 58 safe_fclose(FILE *fp) 59 { 60 if (ferror(fp)) { 61 fclose(fp); 62 return 0; 63 } 64 if (fflush(fp)) { 65 fclose(fp); 66 if (errno == ENOSPC) 67 return 0; 68 fatal("safe_fclose: fflush"); 69 } 70 if (fsync(fileno(fp))) 71 fatal("safe_fclose: fsync"); 72 if (fclose(fp)) 73 fatal("safe_fclose: fclose"); 74 75 return 1; 76 } 77 78 struct passwd * 79 safe_getpwnam(const char *name) 80 { 81 struct passwd *ret; 82 83 ret = getpwnam(name); 84 endpwent(); 85 86 return ret; 87 } 88 89 struct passwd * 90 safe_getpwuid(uid_t uid) 91 { 92 struct passwd *ret; 93 94 ret = getpwuid(uid); 95 endpwent(); 96 97 return ret; 98 } 99 100 int 101 hostname_match(char *hostname, char *pattern) 102 { 103 while (*pattern != '\0' && *hostname != '\0') { 104 if (*pattern == '*') { 105 while (*pattern == '*') 106 pattern++; 107 while (*hostname != '\0' && 108 tolower(*hostname) != tolower(*pattern)) 109 hostname++; 110 continue; 111 } 112 113 if (tolower(*pattern) != tolower(*hostname)) 114 return 0; 115 pattern++; 116 hostname++; 117 } 118 119 return (*hostname == '\0' && *pattern == '\0'); 120 } 121 122 int 123 recipient_to_path(struct path *path, char *recipient) 124 { 125 char *username; 126 char *hostname; 127 128 username = recipient; 129 hostname = strrchr(username, '@'); 130 131 if (username[0] == '\0') { 132 *path->user = '\0'; 133 *path->domain = '\0'; 134 return 1; 135 } 136 137 if (hostname == NULL) { 138 if (strcasecmp(username, "postmaster") != 0) 139 return 0; 140 hostname = "localhost"; 141 } else { 142 *hostname++ = '\0'; 143 } 144 145 if (strlcpy(path->user, username, sizeof(path->user)) 146 >= sizeof(path->user)) 147 return 0; 148 149 if (strlcpy(path->domain, hostname, sizeof(path->domain)) 150 >= sizeof(path->domain)) 151 return 0; 152 153 return 1; 154 } 155 156 int 157 valid_localpart(char *s) 158 { 159 #define IS_ATEXT(c) (isalnum(c) || strchr("!#$%&'*+-/=?^_`{|}~", (c))) 160 nextatom: 161 if (! IS_ATEXT(*s) || *s == '\0') 162 return 0; 163 while (*(++s) != '\0') { 164 if (*s == '.') 165 break; 166 if (IS_ATEXT(*s)) 167 continue; 168 return 0; 169 } 170 if (*s == '.') { 171 s++; 172 goto nextatom; 173 } 174 return 1; 175 } 176 177 int 178 valid_domainpart(char *s) 179 { 180 nextsub: 181 if (!isalnum(*s)) 182 return 0; 183 while (*(++s) != '\0') { 184 if (*s == '.') 185 break; 186 if (isalnum(*s) || *s == '-') 187 continue; 188 return 0; 189 } 190 if (s[-1] == '-') 191 return 0; 192 if (*s == '.') { 193 s++; 194 goto nextsub; 195 } 196 return 1; 197 } 198 199 char * 200 ss_to_text(struct sockaddr_storage *ss) 201 { 202 static char buf[NI_MAXHOST + 5]; 203 char *p; 204 205 buf[0] = '\0'; 206 p = buf; 207 208 if (ss->ss_family == PF_INET6) { 209 strlcpy(buf, "IPv6:", sizeof(buf)); 210 p = buf + 5; 211 } 212 213 if (getnameinfo((struct sockaddr *)ss, ss->ss_len, p, 214 NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) 215 fatalx("ss_to_text: getnameinfo"); 216 217 return (buf); 218 } 219 220 int 221 valid_message_id(char *mid) 222 { 223 u_int8_t cnt; 224 225 /* [0-9]{10}\.[a-zA-Z0-9]{16} */ 226 for (cnt = 0; cnt < 10; ++cnt, ++mid) 227 if (! isdigit((int)*mid)) 228 return 0; 229 230 if (*mid++ != '.') 231 return 0; 232 233 for (cnt = 0; cnt < 16; ++cnt, ++mid) 234 if (! isalnum((int)*mid)) 235 return 0; 236 237 return (*mid == '\0'); 238 } 239 240 int 241 valid_message_uid(char *muid) 242 { 243 u_int8_t cnt; 244 245 /* [0-9]{10}\.[a-zA-Z0-9]{16}\.[0-9]{0,} */ 246 for (cnt = 0; cnt < 10; ++cnt, ++muid) 247 if (! isdigit((int)*muid)) 248 return 0; 249 250 if (*muid++ != '.') 251 return 0; 252 253 for (cnt = 0; cnt < 16; ++cnt, ++muid) 254 if (! isalnum((int)*muid)) 255 return 0; 256 257 if (*muid++ != '.') 258 return 0; 259 260 for (cnt = 0; *muid != '\0'; ++cnt, ++muid) 261 if (! isdigit(*muid)) 262 return 0; 263 264 return (cnt != 0); 265 } 266 267 char * 268 time_to_text(time_t when) 269 { 270 struct tm *lt; 271 static char buf[40]; 272 char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 273 char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", 274 "Jul","Aug","Sep","Oct","Nov","Dec"}; 275 276 lt = localtime(&when); 277 if (lt == NULL || when == 0) 278 fatalx("time_to_text: localtime"); 279 280 /* We do not use strftime because it is subject to locale substitution*/ 281 if (! bsnprintf(buf, sizeof(buf), "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)", 282 day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon], 283 lt->tm_year + 1900, 284 lt->tm_hour, lt->tm_min, lt->tm_sec, 285 lt->tm_gmtoff >= 0 ? '+' : '-', 286 abs((int)lt->tm_gmtoff / 3600), 287 abs((int)lt->tm_gmtoff % 3600) / 60, 288 lt->tm_zone)) 289 fatalx("time_to_text: bsnprintf"); 290 291 return buf; 292 } 293 294 /* 295 * Check file for security. Based on usr.bin/ssh/auth.c. 296 */ 297 int 298 secure_file(int fd, char *path, struct passwd *pw) 299 { 300 char buf[MAXPATHLEN]; 301 char homedir[MAXPATHLEN]; 302 struct stat st; 303 char *cp; 304 305 if (realpath(path, buf) == NULL) 306 return 0; 307 308 if (realpath(pw->pw_dir, homedir) == NULL) 309 homedir[0] = '\0'; 310 311 /* Check the open file to avoid races. */ 312 if (fstat(fd, &st) < 0 || 313 !S_ISREG(st.st_mode) || 314 (st.st_uid != 0 && st.st_uid != pw->pw_uid) || 315 (st.st_mode & 066) != 0) 316 return 0; 317 318 /* For each component of the canonical path, walking upwards. */ 319 for (;;) { 320 if ((cp = dirname(buf)) == NULL) 321 return 0; 322 strlcpy(buf, cp, sizeof(buf)); 323 324 if (stat(buf, &st) < 0 || 325 (st.st_uid != 0 && st.st_uid != pw->pw_uid) || 326 (st.st_mode & 022) != 0) 327 return 0; 328 329 /* We can stop checking after reaching homedir level. */ 330 if (strcmp(homedir, buf) == 0) 331 break; 332 333 /* 334 * dirname should always complete with a "/" path, 335 * but we can be paranoid and check for "." too 336 */ 337 if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) 338 break; 339 } 340 341 return 1; 342 } 343 344 void 345 addargs(arglist *args, char *fmt, ...) 346 { 347 va_list ap; 348 char *cp; 349 u_int nalloc; 350 int r; 351 352 va_start(ap, fmt); 353 r = vasprintf(&cp, fmt, ap); 354 va_end(ap); 355 if (r == -1) 356 fatal("addargs: argument too long"); 357 358 nalloc = args->nalloc; 359 if (args->list == NULL) { 360 nalloc = 32; 361 args->num = 0; 362 } else if (args->num+2 >= nalloc) 363 nalloc *= 2; 364 365 if (SIZE_T_MAX / nalloc < sizeof(char *)) 366 fatalx("addargs: nalloc * size > SIZE_T_MAX"); 367 args->list = realloc(args->list, nalloc * sizeof(char *)); 368 if (args->list == NULL) 369 fatal("addargs: realloc"); 370 args->nalloc = nalloc; 371 args->list[args->num++] = cp; 372 args->list[args->num] = NULL; 373 } 374 375 void 376 lowercase(char *buf, char *s, size_t len) 377 { 378 if (len == 0) 379 fatalx("lowercase: len == 0"); 380 381 if (strlcpy(buf, s, len) >= len) 382 fatalx("lowercase: truncation"); 383 384 while (*buf != '\0') { 385 *buf = tolower(*buf); 386 buf++; 387 } 388 } 389