1 /* $OpenBSD: makemap.c,v 1.49 2014/07/08 13:49:09 eric Exp $ */ 2 3 /* 4 * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> 5 * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 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/stat.h> 22 #include <sys/tree.h> 23 #include <sys/queue.h> 24 #include <sys/socket.h> 25 26 #include <ctype.h> 27 #include <db.h> 28 #include <err.h> 29 #include <errno.h> 30 #include <event.h> 31 #include <fcntl.h> 32 #include <imsg.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <unistd.h> 37 #include <util.h> 38 39 #include "smtpd.h" 40 #include "log.h" 41 42 #define PATH_ALIASES "/etc/mail/aliases" 43 44 extern char *__progname; 45 46 __dead void usage(void); 47 static int parse_map(char *); 48 static int parse_entry(char *, size_t, size_t); 49 static int parse_mapentry(char *, size_t, size_t); 50 static int parse_setentry(char *, size_t, size_t); 51 static int make_plain(DBT *, char *); 52 static int make_aliases(DBT *, char *); 53 static char *conf_aliases(char *); 54 55 DB *db; 56 char *source; 57 char *oflag; 58 int dbputs; 59 60 struct smtpd smtpd; 61 struct smtpd *env = &smtpd; 62 63 enum program { 64 P_MAKEMAP, 65 P_NEWALIASES 66 } mode; 67 68 enum output_type { 69 T_PLAIN, 70 T_ALIASES, 71 T_SET 72 } type; 73 74 /* 75 * Stub functions so that makemap compiles using minimum object files. 76 */ 77 void 78 purge_config(uint8_t what) 79 { 80 memset(env, 0, sizeof(struct smtpd)); 81 } 82 83 int 84 fork_proc_backend(const char *backend, const char *conf, const char *procname) 85 { 86 return (-1); 87 } 88 89 int 90 main(int argc, char *argv[]) 91 { 92 struct stat sb; 93 char dbname[SMTPD_MAXPATHLEN]; 94 char *opts; 95 char *conf; 96 int ch; 97 DBTYPE dbtype = DB_HASH; 98 char *p; 99 mode_t omode; 100 101 log_init(1); 102 103 mode = strcmp(__progname, "newaliases") ? P_MAKEMAP : P_NEWALIASES; 104 conf = CONF_FILE; 105 type = T_PLAIN; 106 opts = "ho:t:d:"; 107 if (mode == P_NEWALIASES) 108 opts = "f:h"; 109 110 while ((ch = getopt(argc, argv, opts)) != -1) { 111 switch (ch) { 112 case 'd': 113 if (strcmp(optarg, "hash") == 0) 114 dbtype = DB_HASH; 115 else if (strcmp(optarg, "btree") == 0) 116 dbtype = DB_BTREE; 117 else if (strcmp(optarg, "dbm") == 0) 118 dbtype = DB_RECNO; 119 else 120 errx(1, "unsupported DB type '%s'", optarg); 121 break; 122 case 'f': 123 conf = optarg; 124 break; 125 case 'o': 126 oflag = optarg; 127 break; 128 case 't': 129 if (strcmp(optarg, "aliases") == 0) 130 type = T_ALIASES; 131 else if (strcmp(optarg, "set") == 0) 132 type = T_SET; 133 else 134 errx(1, "unsupported type '%s'", optarg); 135 break; 136 default: 137 usage(); 138 } 139 } 140 argc -= optind; 141 argv += optind; 142 143 /* sendmail-compat makemap ... re-execute using proper interface */ 144 if (argc == 2) { 145 if (oflag) 146 usage(); 147 148 p = strstr(argv[1], ".db"); 149 if (p == NULL || strcmp(p, ".db") != 0) { 150 if (! bsnprintf(dbname, sizeof dbname, "%s.db", 151 argv[1])) 152 errx(1, "database name too long"); 153 } 154 else { 155 if (strlcpy(dbname, argv[1], sizeof dbname) 156 >= sizeof dbname) 157 errx(1, "database name too long"); 158 } 159 160 execlp("makemap", "makemap", "-d", argv[0], "-o", dbname, "-", 161 NULL); 162 err(1, "execlp"); 163 } 164 165 if (mode == P_NEWALIASES) { 166 if (geteuid()) 167 errx(1, "need root privileges"); 168 if (argc != 0) 169 usage(); 170 type = T_ALIASES; 171 source = conf_aliases(conf); 172 } else { 173 if (argc != 1) 174 usage(); 175 source = argv[0]; 176 } 177 178 if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1) 179 err(1, "asprintf"); 180 181 if (strcmp(source, "-") != 0) 182 if (stat(source, &sb) == -1) 183 err(1, "stat: %s", source); 184 185 if (! bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag)) 186 errx(1, "path too long"); 187 omode = umask(7077); 188 if (mkstemp(dbname) == -1) 189 err(1, "mkstemp"); 190 umask(omode); 191 192 db = dbopen(dbname, O_EXLOCK|O_RDWR|O_SYNC, 0644, dbtype, NULL); 193 if (db == NULL) { 194 warn("dbopen: %s", dbname); 195 goto bad; 196 } 197 198 if (strcmp(source, "-") != 0) 199 if (fchmod(db->fd(db), sb.st_mode) == -1 || 200 fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) { 201 warn("couldn't carry ownership and perms to %s", 202 dbname); 203 goto bad; 204 } 205 206 if (! parse_map(source)) 207 goto bad; 208 209 if (db->close(db) == -1) { 210 warn("dbclose: %s", dbname); 211 goto bad; 212 } 213 214 if (rename(dbname, oflag) == -1) { 215 warn("rename"); 216 goto bad; 217 } 218 219 if (mode == P_NEWALIASES) 220 printf("%s: %d aliases\n", source, dbputs); 221 else if (dbputs == 0) 222 warnx("warning: empty map created: %s", oflag); 223 224 return 0; 225 bad: 226 unlink(dbname); 227 return 1; 228 } 229 230 int 231 parse_map(char *filename) 232 { 233 FILE *fp; 234 char *line; 235 size_t len; 236 size_t lineno = 0; 237 char delim[] = { '\\', 0, 0 }; 238 239 if (strcmp(filename, "-") == 0) 240 fp = fdopen(0, "r"); 241 else 242 fp = fopen(filename, "r"); 243 if (fp == NULL) { 244 warn("%s", filename); 245 return 0; 246 } 247 248 if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) { 249 if (errno == EWOULDBLOCK) 250 warnx("%s is locked", filename); 251 else 252 warn("%s: flock", filename); 253 fclose(fp); 254 return 0; 255 } 256 257 while ((line = fparseln(fp, &len, &lineno, delim, 0)) != NULL) { 258 if (! parse_entry(line, len, lineno)) { 259 free(line); 260 fclose(fp); 261 return 0; 262 } 263 free(line); 264 } 265 266 fclose(fp); 267 return 1; 268 } 269 270 int 271 parse_entry(char *line, size_t len, size_t lineno) 272 { 273 switch (type) { 274 case T_PLAIN: 275 case T_ALIASES: 276 return parse_mapentry(line, len, lineno); 277 case T_SET: 278 return parse_setentry(line, len, lineno); 279 } 280 return 0; 281 } 282 283 int 284 parse_mapentry(char *line, size_t len, size_t lineno) 285 { 286 DBT key; 287 DBT val; 288 char *keyp; 289 char *valp; 290 291 keyp = line; 292 while (isspace((unsigned char)*keyp)) 293 keyp++; 294 if (*keyp == '\0' || *keyp == '#') 295 return 1; 296 297 valp = keyp; 298 strsep(&valp, " \t:"); 299 if (valp == NULL || valp == keyp) 300 goto bad; 301 while (*valp == ':' || isspace((unsigned char)*valp)) 302 valp++; 303 if (*valp == '\0' || *valp == '#') 304 goto bad; 305 306 /* Check for dups. */ 307 key.data = keyp; 308 key.size = strlen(keyp) + 1; 309 310 xlowercase(key.data, key.data, strlen(key.data) + 1); 311 if (db->get(db, &key, &val, 0) == 0) { 312 warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); 313 return 0; 314 } 315 316 if (type == T_PLAIN) { 317 if (! make_plain(&val, valp)) 318 goto bad; 319 } 320 else if (type == T_ALIASES) { 321 if (! make_aliases(&val, valp)) 322 goto bad; 323 } 324 325 if (db->put(db, &key, &val, 0) == -1) { 326 warn("dbput"); 327 return 0; 328 } 329 330 dbputs++; 331 332 free(val.data); 333 334 return 1; 335 336 bad: 337 warnx("%s:%zd: invalid entry", source, lineno); 338 return 0; 339 } 340 341 int 342 parse_setentry(char *line, size_t len, size_t lineno) 343 { 344 DBT key; 345 DBT val; 346 char *keyp; 347 348 keyp = line; 349 while (isspace((unsigned char)*keyp)) 350 keyp++; 351 if (*keyp == '\0' || *keyp == '#') 352 return 1; 353 354 val.data = "<set>"; 355 val.size = strlen(val.data) + 1; 356 357 /* Check for dups. */ 358 key.data = keyp; 359 key.size = strlen(keyp) + 1; 360 xlowercase(key.data, key.data, strlen(key.data) + 1); 361 if (db->get(db, &key, &val, 0) == 0) { 362 warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); 363 return 0; 364 } 365 366 if (db->put(db, &key, &val, 0) == -1) { 367 warn("dbput"); 368 return 0; 369 } 370 371 dbputs++; 372 373 return 1; 374 } 375 376 int 377 make_plain(DBT *val, char *text) 378 { 379 val->data = xstrdup(text, "make_plain"); 380 val->size = strlen(text) + 1; 381 382 return (val->size); 383 } 384 385 int 386 make_aliases(DBT *val, char *text) 387 { 388 struct expandnode xn; 389 char *subrcpt; 390 char *endp; 391 char *origtext; 392 393 val->data = NULL; 394 val->size = 0; 395 396 origtext = xstrdup(text, "make_aliases"); 397 398 while ((subrcpt = strsep(&text, ",")) != NULL) { 399 /* subrcpt: strip initial whitespace. */ 400 while (isspace((unsigned char)*subrcpt)) 401 ++subrcpt; 402 if (*subrcpt == '\0') 403 goto error; 404 405 /* subrcpt: strip trailing whitespace. */ 406 endp = subrcpt + strlen(subrcpt) - 1; 407 while (subrcpt < endp && isspace((unsigned char)*endp)) 408 *endp-- = '\0'; 409 410 if (! text_to_expandnode(&xn, subrcpt)) 411 goto error; 412 } 413 414 val->data = origtext; 415 val->size = strlen(origtext) + 1; 416 return (val->size); 417 418 error: 419 free(origtext); 420 421 return 0; 422 } 423 424 char * 425 conf_aliases(char *cfgpath) 426 { 427 struct table *table; 428 char *path; 429 char *p; 430 431 if (parse_config(env, cfgpath, 0)) 432 exit(1); 433 434 table = table_find("aliases", NULL); 435 if (table == NULL) 436 return (PATH_ALIASES); 437 438 path = xstrdup(table->t_config, "conf_aliases"); 439 p = strstr(path, ".db"); 440 if (p == NULL || strcmp(p, ".db") != 0) { 441 return (path); 442 } 443 *p = '\0'; 444 return (path); 445 } 446 447 void 448 usage(void) 449 { 450 if (mode == P_NEWALIASES) 451 fprintf(stderr, "usage: %s [-f file]\n", __progname); 452 else 453 fprintf(stderr, "usage: %s [-d dbtype] [-o dbfile] " 454 "[-t type] file\n", __progname); 455 exit(1); 456 } 457