1 /* $OpenBSD: util.c,v 1.47 2011/05/17 18:54:32 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 valid_localpart(char *s) 114 { 115 #define IS_ATEXT(c) (isalnum((int)(c)) || strchr("!#$%&'*+-/=?^_`{|}~", (c))) 116 nextatom: 117 if (! IS_ATEXT(*s) || *s == '\0') 118 return 0; 119 while (*(++s) != '\0') { 120 if (*s == '.') 121 break; 122 if (IS_ATEXT(*s)) 123 continue; 124 return 0; 125 } 126 if (*s == '.') { 127 s++; 128 goto nextatom; 129 } 130 return 1; 131 } 132 133 int 134 valid_domainpart(char *s) 135 { 136 nextsub: 137 if (!isalnum((int)*s)) 138 return 0; 139 while (*(++s) != '\0') { 140 if (*s == '.') 141 break; 142 if (isalnum((int)*s) || *s == '-') 143 continue; 144 return 0; 145 } 146 if (s[-1] == '-') 147 return 0; 148 if (*s == '.') { 149 s++; 150 goto nextsub; 151 } 152 return 1; 153 } 154 155 int 156 email_to_mailaddr(struct mailaddr *maddr, char *email) 157 { 158 char *username; 159 char *hostname; 160 161 username = email; 162 hostname = strrchr(username, '@'); 163 164 if (username[0] == '\0') { 165 *maddr->user = '\0'; 166 *maddr->domain = '\0'; 167 return 1; 168 } 169 170 if (hostname == NULL) { 171 if (strcasecmp(username, "postmaster") != 0) 172 return 0; 173 hostname = "localhost"; 174 } else { 175 *hostname++ = '\0'; 176 } 177 178 if (strlcpy(maddr->user, username, sizeof(maddr->user)) 179 >= sizeof(maddr->user)) 180 return 0; 181 182 if (strlcpy(maddr->domain, hostname, sizeof(maddr->domain)) 183 >= sizeof(maddr->domain)) 184 return 0; 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, char *userdir, uid_t uid, 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(userdir, 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 != 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 != 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 envelope_set_errormsg(struct envelope *e, char *fmt, ...) 349 { 350 int ret; 351 va_list ap; 352 353 va_start(ap, fmt); 354 355 ret = vsnprintf(e->delivery.errorline, MAX_LINE_SIZE, fmt, ap); 356 if (ret >= MAX_LINE_SIZE) 357 strlcpy(e->delivery.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 envelope_get_errormsg(struct envelope *e) 368 { 369 return e->delivery.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 u_int64_t 399 generate_uid(void) 400 { 401 u_int64_t id; 402 struct timeval tp; 403 404 if (gettimeofday(&tp, NULL) == -1) 405 fatal("generate_uid: time"); 406 407 id = (u_int32_t)tp.tv_sec; 408 id <<= 32; 409 id |= (u_int32_t)tp.tv_usec; 410 usleep(1); 411 412 return (id); 413 } 414 415 void 416 fdlimit(double percent) 417 { 418 struct rlimit rl; 419 420 if (percent < 0 || percent > 1) 421 fatalx("fdlimit: parameter out of range"); 422 if (getrlimit(RLIMIT_NOFILE, &rl) == -1) 423 fatal("fdlimit: getrlimit"); 424 rl.rlim_cur = percent * rl.rlim_max; 425 if (setrlimit(RLIMIT_NOFILE, &rl) == -1) 426 fatal("fdlimit: setrlimit"); 427 } 428 429 int 430 availdesc(void) 431 { 432 int avail; 433 434 avail = getdtablesize(); 435 avail -= 3; /* stdin, stdout, stderr */ 436 avail -= PROC_COUNT; /* imsg channels */ 437 avail -= 5; /* safety buffer */ 438 439 return (avail); 440 } 441 442 void 443 session_socket_blockmode(int fd, enum blockmodes bm) 444 { 445 int flags; 446 447 if ((flags = fcntl(fd, F_GETFL, 0)) == -1) 448 fatal("fcntl F_GETFL"); 449 450 if (bm == BM_NONBLOCK) 451 flags |= O_NONBLOCK; 452 else 453 flags &= ~O_NONBLOCK; 454 455 if ((flags = fcntl(fd, F_SETFL, flags)) == -1) 456 fatal("fcntl F_SETFL"); 457 } 458 459 void 460 session_socket_no_linger(int fd) 461 { 462 struct linger lng; 463 464 bzero(&lng, sizeof(lng)); 465 if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) 466 fatal("session_socket_no_linger"); 467 } 468 469 int 470 session_socket_error(int fd) 471 { 472 int error; 473 socklen_t len; 474 475 len = sizeof(error); 476 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) 477 fatal("session_socket_error: getsockopt"); 478 479 return (error); 480 } 481 482 const char * 483 log_in6addr(const struct in6_addr *addr) 484 { 485 struct sockaddr_in6 sa_in6; 486 u_int16_t tmp16; 487 488 bzero(&sa_in6, sizeof(sa_in6)); 489 sa_in6.sin6_len = sizeof(sa_in6); 490 sa_in6.sin6_family = AF_INET6; 491 memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr)); 492 493 /* XXX thanks, KAME, for this ugliness... adopted from route/show.c */ 494 if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) || 495 IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) { 496 memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16)); 497 sa_in6.sin6_scope_id = ntohs(tmp16); 498 sa_in6.sin6_addr.s6_addr[2] = 0; 499 sa_in6.sin6_addr.s6_addr[3] = 0; 500 } 501 502 return (log_sockaddr((struct sockaddr *)&sa_in6)); 503 } 504 505 const char * 506 log_sockaddr(struct sockaddr *sa) 507 { 508 static char buf[NI_MAXHOST]; 509 510 if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0, 511 NI_NUMERICHOST)) 512 return ("(unknown)"); 513 else 514 return (buf); 515 } 516 517 u_int32_t 518 filename_to_msgid(char *filename) 519 { 520 u_int32_t ulval; 521 char *ep; 522 523 errno = 0; 524 ulval = strtoul(filename, &ep, 16); 525 if (filename[0] == '\0' || *ep != '\0') 526 return 0; 527 if (errno == ERANGE && ulval == 0xffffffff) 528 return 0; 529 530 return ulval; 531 } 532 533 u_int64_t 534 filename_to_evpid(char *filename) 535 { 536 u_int64_t ullval; 537 char *ep; 538 539 errno = 0; 540 ullval = strtoull(filename, &ep, 16); 541 if (filename[0] == '\0' || *ep != '\0') 542 return 0; 543 if (errno == ERANGE && ullval == ULLONG_MAX) 544 return 0; 545 546 return ullval; 547 } 548 549 u_int32_t 550 evpid_to_msgid(u_int64_t evpid) 551 { 552 return (evpid >> 32); 553 } 554 555 u_int64_t 556 msgid_to_evpid(u_int32_t msgid) 557 { 558 return ((u_int64_t)msgid << 32); 559 } 560