1 /* $OpenBSD: util.c,v 1.44 2011/04/17 13:36:07 gilles Exp $ */ 2 3 /* 4 * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. 5 * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> 6 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 */ 20 21 #include <sys/types.h> 22 #include <sys/param.h> 23 #include <sys/queue.h> 24 #include <sys/tree.h> 25 #include <sys/socket.h> 26 #include <sys/stat.h> 27 #include <sys/resource.h> 28 29 #include <netinet/in.h> 30 31 #include <ctype.h> 32 #include <err.h> 33 #include <errno.h> 34 #include <event.h> 35 #include <fcntl.h> 36 #include <imsg.h> 37 #include <libgen.h> 38 #include <netdb.h> 39 #include <pwd.h> 40 #include <stdarg.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <time.h> 45 #include <unistd.h> 46 47 #include "smtpd.h" 48 #include "log.h" 49 50 const char *log_in6addr(const struct in6_addr *); 51 const char *log_sockaddr(struct sockaddr *); 52 53 int 54 bsnprintf(char *str, size_t size, const char *format, ...) 55 { 56 int ret; 57 va_list ap; 58 59 va_start(ap, format); 60 ret = vsnprintf(str, size, format, ap); 61 va_end(ap); 62 if (ret == -1 || ret >= (int)size) 63 return 0; 64 65 return 1; 66 } 67 68 /* Close file, signifying temporary error condition (if any) to the caller. */ 69 int 70 safe_fclose(FILE *fp) 71 { 72 if (ferror(fp)) { 73 fclose(fp); 74 return 0; 75 } 76 if (fflush(fp)) { 77 fclose(fp); 78 if (errno == ENOSPC) 79 return 0; 80 fatal("safe_fclose: fflush"); 81 } 82 if (fsync(fileno(fp))) 83 fatal("safe_fclose: fsync"); 84 if (fclose(fp)) 85 fatal("safe_fclose: fclose"); 86 87 return 1; 88 } 89 90 int 91 hostname_match(char *hostname, char *pattern) 92 { 93 while (*pattern != '\0' && *hostname != '\0') { 94 if (*pattern == '*') { 95 while (*pattern == '*') 96 pattern++; 97 while (*hostname != '\0' && 98 tolower((int)*hostname) != tolower((int)*pattern)) 99 hostname++; 100 continue; 101 } 102 103 if (tolower((int)*pattern) != tolower((int)*hostname)) 104 return 0; 105 pattern++; 106 hostname++; 107 } 108 109 return (*hostname == '\0' && *pattern == '\0'); 110 } 111 112 int 113 recipient_to_path(struct path *path, char *recipient) 114 { 115 char *username; 116 char *hostname; 117 118 username = recipient; 119 hostname = strrchr(username, '@'); 120 121 if (username[0] == '\0') { 122 *path->user = '\0'; 123 *path->domain = '\0'; 124 return 1; 125 } 126 127 if (hostname == NULL) { 128 if (strcasecmp(username, "postmaster") != 0) 129 return 0; 130 hostname = "localhost"; 131 } else { 132 *hostname++ = '\0'; 133 } 134 135 if (strlcpy(path->user, username, sizeof(path->user)) 136 >= sizeof(path->user)) 137 return 0; 138 139 if (strlcpy(path->domain, hostname, sizeof(path->domain)) 140 >= sizeof(path->domain)) 141 return 0; 142 143 return 1; 144 } 145 146 int 147 valid_localpart(char *s) 148 { 149 #define IS_ATEXT(c) (isalnum((int)(c)) || strchr("!#$%&'*+-/=?^_`{|}~", (c))) 150 nextatom: 151 if (! IS_ATEXT(*s) || *s == '\0') 152 return 0; 153 while (*(++s) != '\0') { 154 if (*s == '.') 155 break; 156 if (IS_ATEXT(*s)) 157 continue; 158 return 0; 159 } 160 if (*s == '.') { 161 s++; 162 goto nextatom; 163 } 164 return 1; 165 } 166 167 int 168 valid_domainpart(char *s) 169 { 170 nextsub: 171 if (!isalnum((int)*s)) 172 return 0; 173 while (*(++s) != '\0') { 174 if (*s == '.') 175 break; 176 if (isalnum((int)*s) || *s == '-') 177 continue; 178 return 0; 179 } 180 if (s[-1] == '-') 181 return 0; 182 if (*s == '.') { 183 s++; 184 goto nextsub; 185 } 186 return 1; 187 } 188 189 char * 190 ss_to_text(struct sockaddr_storage *ss) 191 { 192 static char buf[NI_MAXHOST + 5]; 193 char *p; 194 195 buf[0] = '\0'; 196 p = buf; 197 198 if (ss->ss_family == PF_INET) { 199 in_addr_t addr; 200 201 addr = ((struct sockaddr_in *)ss)->sin_addr.s_addr; 202 addr = ntohl(addr); 203 bsnprintf(p, NI_MAXHOST, 204 "%d.%d.%d.%d", 205 (addr >> 24) & 0xff, 206 (addr >> 16) & 0xff, 207 (addr >> 8) & 0xff, 208 addr & 0xff); 209 } 210 211 if (ss->ss_family == PF_INET6) { 212 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ss; 213 struct in6_addr *in6_addr; 214 215 strlcpy(buf, "IPv6:", sizeof(buf)); 216 p = buf + 5; 217 in6_addr = &in6->sin6_addr; 218 bsnprintf(p, NI_MAXHOST, "%s", log_in6addr(in6_addr)); 219 } 220 221 return (buf); 222 } 223 224 char * 225 time_to_text(time_t when) 226 { 227 struct tm *lt; 228 static char buf[40]; 229 char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 230 char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", 231 "Jul","Aug","Sep","Oct","Nov","Dec"}; 232 233 lt = localtime(&when); 234 if (lt == NULL || when == 0) 235 fatalx("time_to_text: localtime"); 236 237 /* We do not use strftime because it is subject to locale substitution*/ 238 if (! bsnprintf(buf, sizeof(buf), "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)", 239 day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon], 240 lt->tm_year + 1900, 241 lt->tm_hour, lt->tm_min, lt->tm_sec, 242 lt->tm_gmtoff >= 0 ? '+' : '-', 243 abs((int)lt->tm_gmtoff / 3600), 244 abs((int)lt->tm_gmtoff % 3600) / 60, 245 lt->tm_zone)) 246 fatalx("time_to_text: bsnprintf"); 247 248 return buf; 249 } 250 251 /* 252 * Check file for security. Based on usr.bin/ssh/auth.c. 253 */ 254 int 255 secure_file(int fd, char *path, struct passwd *pw, int mayread) 256 { 257 char buf[MAXPATHLEN]; 258 char homedir[MAXPATHLEN]; 259 struct stat st; 260 char *cp; 261 262 if (realpath(path, buf) == NULL) 263 return 0; 264 265 if (realpath(pw->pw_dir, homedir) == NULL) 266 homedir[0] = '\0'; 267 268 /* Check the open file to avoid races. */ 269 if (fstat(fd, &st) < 0 || 270 !S_ISREG(st.st_mode) || 271 (st.st_uid != 0 && st.st_uid != pw->pw_uid) || 272 (st.st_mode & (mayread ? 022 : 066)) != 0) 273 return 0; 274 275 /* For each component of the canonical path, walking upwards. */ 276 for (;;) { 277 if ((cp = dirname(buf)) == NULL) 278 return 0; 279 strlcpy(buf, cp, sizeof(buf)); 280 281 if (stat(buf, &st) < 0 || 282 (st.st_uid != 0 && st.st_uid != pw->pw_uid) || 283 (st.st_mode & 022) != 0) 284 return 0; 285 286 /* We can stop checking after reaching homedir level. */ 287 if (strcmp(homedir, buf) == 0) 288 break; 289 290 /* 291 * dirname should always complete with a "/" path, 292 * but we can be paranoid and check for "." too 293 */ 294 if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) 295 break; 296 } 297 298 return 1; 299 } 300 301 void 302 addargs(arglist *args, char *fmt, ...) 303 { 304 va_list ap; 305 char *cp; 306 u_int nalloc; 307 int r; 308 309 va_start(ap, fmt); 310 r = vasprintf(&cp, fmt, ap); 311 va_end(ap); 312 if (r == -1) 313 fatal("addargs: argument too long"); 314 315 nalloc = args->nalloc; 316 if (args->list == NULL) { 317 nalloc = 32; 318 args->num = 0; 319 } else if (args->num+2 >= nalloc) 320 nalloc *= 2; 321 322 if (SIZE_T_MAX / nalloc < sizeof(char *)) 323 fatalx("addargs: nalloc * size > SIZE_T_MAX"); 324 args->list = realloc(args->list, nalloc * sizeof(char *)); 325 if (args->list == NULL) 326 fatal("addargs: realloc"); 327 args->nalloc = nalloc; 328 args->list[args->num++] = cp; 329 args->list[args->num] = NULL; 330 } 331 332 void 333 lowercase(char *buf, char *s, size_t len) 334 { 335 if (len == 0) 336 fatalx("lowercase: len == 0"); 337 338 if (strlcpy(buf, s, len) >= len) 339 fatalx("lowercase: truncation"); 340 341 while (*buf != '\0') { 342 *buf = tolower((int)*buf); 343 buf++; 344 } 345 } 346 347 void 348 message_set_errormsg(struct envelope *m, char *fmt, ...) 349 { 350 int ret; 351 va_list ap; 352 353 va_start(ap, fmt); 354 355 ret = vsnprintf(m->session_errorline, MAX_LINE_SIZE, fmt, ap); 356 if (ret >= MAX_LINE_SIZE) 357 strlcpy(m->session_errorline + (MAX_LINE_SIZE - 4), "...", 4); 358 359 /* this should not happen */ 360 if (ret == -1) 361 err(1, "vsnprintf"); 362 363 va_end(ap); 364 } 365 366 char * 367 message_get_errormsg(struct envelope *m) 368 { 369 return m->session_errorline; 370 } 371 372 void 373 sa_set_port(struct sockaddr *sa, int port) 374 { 375 char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; 376 struct addrinfo hints, *res; 377 int error; 378 379 error = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); 380 if (error) 381 fatalx("sa_set_port: getnameinfo failed"); 382 383 memset(&hints, 0, sizeof(hints)); 384 hints.ai_family = PF_UNSPEC; 385 hints.ai_socktype = SOCK_STREAM; 386 hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; 387 388 snprintf(sbuf, sizeof(sbuf), "%d", port); 389 390 error = getaddrinfo(hbuf, sbuf, &hints, &res); 391 if (error) 392 fatalx("sa_set_port: getaddrinfo failed"); 393 394 memcpy(sa, res->ai_addr, res->ai_addrlen); 395 freeaddrinfo(res); 396 } 397 398 struct path * 399 path_dup(struct path *path) 400 { 401 struct path *pathp; 402 403 pathp = calloc(sizeof(struct path), 1); 404 if (pathp == NULL) 405 fatal("calloc"); 406 407 *pathp = *path; 408 409 return pathp; 410 } 411 412 u_int64_t 413 generate_uid(void) 414 { 415 u_int64_t id; 416 struct timeval tp; 417 418 if (gettimeofday(&tp, NULL) == -1) 419 fatal("generate_uid: time"); 420 421 id = (u_int32_t)tp.tv_sec; 422 id <<= 32; 423 id |= (u_int32_t)tp.tv_usec; 424 usleep(1); 425 426 return (id); 427 } 428 429 void 430 fdlimit(double percent) 431 { 432 struct rlimit rl; 433 434 if (percent < 0 || percent > 1) 435 fatalx("fdlimit: parameter out of range"); 436 if (getrlimit(RLIMIT_NOFILE, &rl) == -1) 437 fatal("fdlimit: getrlimit"); 438 rl.rlim_cur = percent * rl.rlim_max; 439 if (setrlimit(RLIMIT_NOFILE, &rl) == -1) 440 fatal("fdlimit: setrlimit"); 441 } 442 443 int 444 availdesc(void) 445 { 446 int avail; 447 448 avail = getdtablesize(); 449 avail -= 3; /* stdin, stdout, stderr */ 450 avail -= PROC_COUNT; /* imsg channels */ 451 avail -= 5; /* safety buffer */ 452 453 return (avail); 454 } 455 456 void 457 session_socket_blockmode(int fd, enum blockmodes bm) 458 { 459 int flags; 460 461 if ((flags = fcntl(fd, F_GETFL, 0)) == -1) 462 fatal("fcntl F_GETFL"); 463 464 if (bm == BM_NONBLOCK) 465 flags |= O_NONBLOCK; 466 else 467 flags &= ~O_NONBLOCK; 468 469 if ((flags = fcntl(fd, F_SETFL, flags)) == -1) 470 fatal("fcntl F_SETFL"); 471 } 472 473 void 474 session_socket_no_linger(int fd) 475 { 476 struct linger lng; 477 478 bzero(&lng, sizeof(lng)); 479 if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) 480 fatal("session_socket_no_linger"); 481 } 482 483 int 484 session_socket_error(int fd) 485 { 486 int error, len; 487 488 len = sizeof(error); 489 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) 490 fatal("session_socket_error: getsockopt"); 491 492 return (error); 493 } 494 495 const char * 496 log_in6addr(const struct in6_addr *addr) 497 { 498 struct sockaddr_in6 sa_in6; 499 u_int16_t tmp16; 500 501 bzero(&sa_in6, sizeof(sa_in6)); 502 sa_in6.sin6_len = sizeof(sa_in6); 503 sa_in6.sin6_family = AF_INET6; 504 memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr)); 505 506 /* XXX thanks, KAME, for this ugliness... adopted from route/show.c */ 507 if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) || 508 IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) { 509 memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16)); 510 sa_in6.sin6_scope_id = ntohs(tmp16); 511 sa_in6.sin6_addr.s6_addr[2] = 0; 512 sa_in6.sin6_addr.s6_addr[3] = 0; 513 } 514 515 return (log_sockaddr((struct sockaddr *)&sa_in6)); 516 } 517 518 const char * 519 log_sockaddr(struct sockaddr *sa) 520 { 521 static char buf[NI_MAXHOST]; 522 523 if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, 524 NI_NUMERICHOST)) 525 return ("(unknown)"); 526 else 527 return (buf); 528 } 529 530 u_int32_t 531 filename_to_msgid(char *filename) 532 { 533 u_int32_t ulval; 534 char *ep; 535 536 errno = 0; 537 ulval = strtoul(filename, &ep, 16); 538 if (filename[0] == '\0' || *ep != '\0') 539 return 0; 540 if (errno == ERANGE && ulval == 0xffffffff) 541 return 0; 542 543 return ulval; 544 } 545 546 u_int64_t 547 filename_to_evpid(char *filename) 548 { 549 u_int64_t ullval; 550 char *ep; 551 552 errno = 0; 553 ullval = strtoull(filename, &ep, 16); 554 if (filename[0] == '\0' || *ep != '\0') 555 return 0; 556 if (errno == ERANGE && ullval == ULLONG_MAX) 557 return 0; 558 559 return ullval; 560 } 561 562 u_int32_t 563 evpid_to_msgid(u_int64_t evpid) 564 { 565 return (evpid >> 32); 566 } 567 568 u_int64_t 569 msgid_to_evpid(u_int32_t msgid) 570 { 571 return ((u_int64_t)msgid << 32); 572 } 573