1 /* $OpenBSD: util.c,v 1.27 2009/09/15 16:50:06 jacekm 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 28 #include <err.h> 29 #include <ctype.h> 30 #include <errno.h> 31 #include <event.h> 32 #include <libgen.h> 33 #include <netdb.h> 34 #include <pwd.h> 35 #include <stdarg.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <time.h> 40 #include <unistd.h> 41 42 #include "smtpd.h" 43 44 int 45 bsnprintf(char *str, size_t size, const char *format, ...) 46 { 47 int ret; 48 va_list ap; 49 50 va_start(ap, format); 51 ret = vsnprintf(str, size, format, ap); 52 va_end(ap); 53 if (ret == -1 || ret >= (int)size) 54 return 0; 55 56 return 1; 57 } 58 59 /* Close file, signifying temporary error condition (if any) to the caller. */ 60 int 61 safe_fclose(FILE *fp) 62 { 63 if (ferror(fp)) { 64 fclose(fp); 65 return 0; 66 } 67 if (fflush(fp)) { 68 fclose(fp); 69 if (errno == ENOSPC) 70 return 0; 71 fatal("safe_fclose: fflush"); 72 } 73 if (fsync(fileno(fp))) 74 fatal("safe_fclose: fsync"); 75 if (fclose(fp)) 76 fatal("safe_fclose: fclose"); 77 78 return 1; 79 } 80 81 int 82 hostname_match(char *hostname, char *pattern) 83 { 84 while (*pattern != '\0' && *hostname != '\0') { 85 if (*pattern == '*') { 86 while (*pattern == '*') 87 pattern++; 88 while (*hostname != '\0' && 89 tolower((int)*hostname) != tolower((int)*pattern)) 90 hostname++; 91 continue; 92 } 93 94 if (tolower((int)*pattern) != tolower((int)*hostname)) 95 return 0; 96 pattern++; 97 hostname++; 98 } 99 100 return (*hostname == '\0' && *pattern == '\0'); 101 } 102 103 int 104 recipient_to_path(struct path *path, char *recipient) 105 { 106 char *username; 107 char *hostname; 108 109 username = recipient; 110 hostname = strrchr(username, '@'); 111 112 if (username[0] == '\0') { 113 *path->user = '\0'; 114 *path->domain = '\0'; 115 return 1; 116 } 117 118 if (hostname == NULL) { 119 if (strcasecmp(username, "postmaster") != 0) 120 return 0; 121 hostname = "localhost"; 122 } else { 123 *hostname++ = '\0'; 124 } 125 126 if (strlcpy(path->user, username, sizeof(path->user)) 127 >= sizeof(path->user)) 128 return 0; 129 130 if (strlcpy(path->domain, hostname, sizeof(path->domain)) 131 >= sizeof(path->domain)) 132 return 0; 133 134 return 1; 135 } 136 137 int 138 valid_localpart(char *s) 139 { 140 #define IS_ATEXT(c) (isalnum((int)(c)) || strchr("!#$%&'*+-/=?^_`{|}~", (c))) 141 nextatom: 142 if (! IS_ATEXT(*s) || *s == '\0') 143 return 0; 144 while (*(++s) != '\0') { 145 if (*s == '.') 146 break; 147 if (IS_ATEXT(*s)) 148 continue; 149 return 0; 150 } 151 if (*s == '.') { 152 s++; 153 goto nextatom; 154 } 155 return 1; 156 } 157 158 int 159 valid_domainpart(char *s) 160 { 161 nextsub: 162 if (!isalnum((int)*s)) 163 return 0; 164 while (*(++s) != '\0') { 165 if (*s == '.') 166 break; 167 if (isalnum((int)*s) || *s == '-') 168 continue; 169 return 0; 170 } 171 if (s[-1] == '-') 172 return 0; 173 if (*s == '.') { 174 s++; 175 goto nextsub; 176 } 177 return 1; 178 } 179 180 char * 181 ss_to_text(struct sockaddr_storage *ss) 182 { 183 static char buf[NI_MAXHOST + 5]; 184 char *p; 185 186 buf[0] = '\0'; 187 p = buf; 188 189 if (ss->ss_family == PF_INET6) { 190 strlcpy(buf, "IPv6:", sizeof(buf)); 191 p = buf + 5; 192 } 193 194 if (getnameinfo((struct sockaddr *)ss, ss->ss_len, p, 195 NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) 196 fatalx("ss_to_text: getnameinfo"); 197 198 return (buf); 199 } 200 201 int 202 valid_message_id(char *mid) 203 { 204 u_int8_t cnt; 205 206 /* [0-9]{10}\.[a-zA-Z0-9]{16} */ 207 for (cnt = 0; cnt < 10; ++cnt, ++mid) 208 if (! isdigit((int)*mid)) 209 return 0; 210 211 if (*mid++ != '.') 212 return 0; 213 214 for (cnt = 0; cnt < 16; ++cnt, ++mid) 215 if (! isalnum((int)*mid)) 216 return 0; 217 218 return (*mid == '\0'); 219 } 220 221 int 222 valid_message_uid(char *muid) 223 { 224 u_int8_t cnt; 225 226 /* [0-9]{10}\.[a-zA-Z0-9]{16}\.[0-9]{0,} */ 227 for (cnt = 0; cnt < 10; ++cnt, ++muid) 228 if (! isdigit((int)*muid)) 229 return 0; 230 231 if (*muid++ != '.') 232 return 0; 233 234 for (cnt = 0; cnt < 16; ++cnt, ++muid) 235 if (! isalnum((int)*muid)) 236 return 0; 237 238 if (*muid++ != '.') 239 return 0; 240 241 for (cnt = 0; *muid != '\0'; ++cnt, ++muid) 242 if (! isdigit((int)*muid)) 243 return 0; 244 245 return (cnt != 0); 246 } 247 248 char * 249 time_to_text(time_t when) 250 { 251 struct tm *lt; 252 static char buf[40]; 253 char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 254 char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", 255 "Jul","Aug","Sep","Oct","Nov","Dec"}; 256 257 lt = localtime(&when); 258 if (lt == NULL || when == 0) 259 fatalx("time_to_text: localtime"); 260 261 /* We do not use strftime because it is subject to locale substitution*/ 262 if (! bsnprintf(buf, sizeof(buf), "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)", 263 day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon], 264 lt->tm_year + 1900, 265 lt->tm_hour, lt->tm_min, lt->tm_sec, 266 lt->tm_gmtoff >= 0 ? '+' : '-', 267 abs((int)lt->tm_gmtoff / 3600), 268 abs((int)lt->tm_gmtoff % 3600) / 60, 269 lt->tm_zone)) 270 fatalx("time_to_text: bsnprintf"); 271 272 return buf; 273 } 274 275 /* 276 * Check file for security. Based on usr.bin/ssh/auth.c. 277 */ 278 int 279 secure_file(int fd, char *path, struct passwd *pw, int mayread) 280 { 281 char buf[MAXPATHLEN]; 282 char homedir[MAXPATHLEN]; 283 struct stat st; 284 char *cp; 285 286 if (realpath(path, buf) == NULL) 287 return 0; 288 289 if (realpath(pw->pw_dir, homedir) == NULL) 290 homedir[0] = '\0'; 291 292 /* Check the open file to avoid races. */ 293 if (fstat(fd, &st) < 0 || 294 !S_ISREG(st.st_mode) || 295 (st.st_uid != 0 && st.st_uid != pw->pw_uid) || 296 (st.st_mode & (mayread ? 022 : 066)) != 0) 297 return 0; 298 299 /* For each component of the canonical path, walking upwards. */ 300 for (;;) { 301 if ((cp = dirname(buf)) == NULL) 302 return 0; 303 strlcpy(buf, cp, sizeof(buf)); 304 305 if (stat(buf, &st) < 0 || 306 (st.st_uid != 0 && st.st_uid != pw->pw_uid) || 307 (st.st_mode & 022) != 0) 308 return 0; 309 310 /* We can stop checking after reaching homedir level. */ 311 if (strcmp(homedir, buf) == 0) 312 break; 313 314 /* 315 * dirname should always complete with a "/" path, 316 * but we can be paranoid and check for "." too 317 */ 318 if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) 319 break; 320 } 321 322 return 1; 323 } 324 325 void 326 addargs(arglist *args, char *fmt, ...) 327 { 328 va_list ap; 329 char *cp; 330 u_int nalloc; 331 int r; 332 333 va_start(ap, fmt); 334 r = vasprintf(&cp, fmt, ap); 335 va_end(ap); 336 if (r == -1) 337 fatal("addargs: argument too long"); 338 339 nalloc = args->nalloc; 340 if (args->list == NULL) { 341 nalloc = 32; 342 args->num = 0; 343 } else if (args->num+2 >= nalloc) 344 nalloc *= 2; 345 346 if (SIZE_T_MAX / nalloc < sizeof(char *)) 347 fatalx("addargs: nalloc * size > SIZE_T_MAX"); 348 args->list = realloc(args->list, nalloc * sizeof(char *)); 349 if (args->list == NULL) 350 fatal("addargs: realloc"); 351 args->nalloc = nalloc; 352 args->list[args->num++] = cp; 353 args->list[args->num] = NULL; 354 } 355 356 void 357 lowercase(char *buf, char *s, size_t len) 358 { 359 if (len == 0) 360 fatalx("lowercase: len == 0"); 361 362 if (strlcpy(buf, s, len) >= len) 363 fatalx("lowercase: truncation"); 364 365 while (*buf != '\0') { 366 *buf = tolower((int)*buf); 367 buf++; 368 } 369 } 370 371 void 372 message_set_errormsg(struct message *messagep, char *fmt, ...) 373 { 374 int ret; 375 va_list ap; 376 377 va_start(ap, fmt); 378 379 ret = vsnprintf(messagep->session_errorline, MAX_LINE_SIZE, fmt, ap); 380 if (ret >= MAX_LINE_SIZE) 381 strlcpy(messagep->session_errorline + (MAX_LINE_SIZE - 4), "...", 4); 382 383 /* this should not happen */ 384 if (ret == -1) 385 err(1, "vsnprintf"); 386 387 va_end(ap); 388 } 389 390 char * 391 message_get_errormsg(struct message *messagep) 392 { 393 return messagep->session_errorline; 394 } 395 396 void 397 sa_set_port(struct sockaddr *sa, int port) 398 { 399 char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; 400 struct addrinfo hints, *res; 401 int error; 402 403 error = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); 404 if (error) 405 fatalx("sa_set_port: getnameinfo failed"); 406 407 memset(&hints, 0, sizeof(hints)); 408 hints.ai_family = PF_UNSPEC; 409 hints.ai_socktype = SOCK_STREAM; 410 hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; 411 412 snprintf(sbuf, sizeof(sbuf), "%d", port); 413 414 error = getaddrinfo(hbuf, sbuf, &hints, &res); 415 if (error) 416 fatalx("sa_set_port: getaddrinfo failed"); 417 418 memcpy(sa, res->ai_addr, res->ai_addrlen); 419 freeaddrinfo(res); 420 } 421