150581Seric /* 250581Seric * Copyright (c) 1983 Eric P. Allman 350581Seric * Copyright (c) 1988 Regents of the University of California. 450581Seric * All rights reserved. 550581Seric * 650581Seric * %sccs.include.redist.c% 750581Seric */ 850581Seric 950581Seric #ifndef lint 1051360Seric #ifdef USERDB 11*58151Seric static char sccsid [] = "@(#)udb.c 6.11 (Berkeley) 02/23/93 (with USERDB)"; 1251360Seric #else 13*58151Seric static char sccsid [] = "@(#)udb.c 6.11 (Berkeley) 02/23/93 (without USERDB)"; 1450581Seric #endif 1551360Seric #endif 1650581Seric 1750581Seric #include "sendmail.h" 1850581Seric 1950581Seric #ifdef USERDB 2050581Seric 2151360Seric #include <sys/time.h> 2251923Seric #include <errno.h> 2351360Seric #include <fcntl.h> 2451360Seric #include <netdb.h> 2550581Seric #include <db.h> 2650581Seric 2750581Seric /* 2853654Seric ** UDB.C -- interface between sendmail and Berkeley User Data Base. 2950581Seric ** 3051363Seric ** This depends on the 4.4BSD db package. 3150581Seric */ 3250581Seric 3351362Seric 3451360Seric struct udbent 3551360Seric { 3651360Seric char *udb_spec; /* string version of spec */ 3751360Seric int udb_type; /* type of entry */ 3851951Seric char *udb_default; /* default host for outgoing mail */ 3951360Seric union 4051360Seric { 4151360Seric /* type UE_REMOTE -- do remote call for lookup */ 4251360Seric struct 4351360Seric { 4451360Seric struct sockaddr_in _udb_addr; /* address */ 4551360Seric int _udb_timeout; /* timeout */ 4651360Seric } udb_remote; 4751360Seric #define udb_addr udb_u.udb_remote._udb_addr 4851360Seric #define udb_timeout udb_u.udb_remote._udb_timeout 4951360Seric 5051360Seric /* type UE_FORWARD -- forward message to remote */ 5151360Seric struct 5251360Seric { 5351360Seric char *_udb_fwdhost; /* name of forward host */ 5451360Seric } udb_forward; 5551360Seric #define udb_fwdhost udb_u.udb_forward._udb_fwdhost 5651360Seric 5751951Seric /* type UE_FETCH -- lookup in local database */ 5851360Seric struct 5951360Seric { 6051360Seric char *_udb_dbname; /* pathname of database */ 6151360Seric DB *_udb_dbp; /* open database ptr */ 6251360Seric } udb_lookup; 6351360Seric #define udb_dbname udb_u.udb_lookup._udb_dbname 6451360Seric #define udb_dbp udb_u.udb_lookup._udb_dbp 6551360Seric } udb_u; 6651360Seric }; 6751360Seric 6851360Seric #define UDB_EOLIST 0 /* end of list */ 6951360Seric #define UDB_SKIP 1 /* skip this entry */ 7051360Seric #define UDB_REMOTE 2 /* look up in remote database */ 7151951Seric #define UDB_DBFETCH 3 /* look up in local database */ 7251360Seric #define UDB_FORWARD 4 /* forward to remote host */ 7351360Seric 7451360Seric #define MAXUDBENT 10 /* maximum number of UDB entries */ 7551360Seric 7651363Seric 7751363Seric struct option 7851363Seric { 7951363Seric char *name; 8051363Seric char *val; 8151363Seric }; 8251363Seric /* 8351363Seric ** UDBEXPAND -- look up user in database and expand 8451363Seric ** 8551363Seric ** Parameters: 8651363Seric ** a -- address to expand. 8751363Seric ** sendq -- pointer to head of sendq to put the expansions in. 8851363Seric ** 8951363Seric ** Returns: 9051923Seric ** EX_TEMPFAIL -- if something "odd" happened -- probably due 9151923Seric ** to accessing a file on an NFS server that is down. 9251923Seric ** EX_OK -- otherwise. 9351363Seric ** 9451363Seric ** Side Effects: 9551363Seric ** Modifies sendq. 9651363Seric */ 9751363Seric 9851363Seric int UdbPort = 1616; 9951363Seric int UdbTimeout = 10; 10051363Seric 10151953Seric struct udbent UdbEnts[MAXUDBENT + 1]; 10251953Seric int UdbSock = -1; 10351953Seric bool UdbInitialized = FALSE; 10451360Seric 10551923Seric int 10655012Seric udbexpand(a, sendq, e) 10750581Seric register ADDRESS *a; 10850581Seric ADDRESS **sendq; 10955012Seric register ENVELOPE *e; 11050581Seric { 11150581Seric int i; 11250581Seric register char *p; 11350581Seric DBT key; 11450581Seric DBT info; 11551360Seric bool breakout; 11651360Seric register struct udbent *up; 11751362Seric int keylen; 11858082Seric int naddrs; 11957232Seric char keybuf[MAXKEY]; 12057232Seric char buf[BUFSIZ]; 12150581Seric 12250581Seric if (tTd(28, 1)) 12358065Seric printf("udbexpand(%s)\n", a->q_paddr); 12450581Seric 12550581Seric /* make certain we are supposed to send to this address */ 12651909Seric if (bitset(QDONTSEND, a->q_flags)) 12751923Seric return EX_OK; 12855012Seric e->e_to = a->q_paddr; 12950581Seric 13051360Seric /* on first call, locate the database */ 13151951Seric if (!UdbInitialized) 13250581Seric { 13351923Seric extern int _udbx_init(); 13451362Seric 13551923Seric if (_udbx_init() == EX_TEMPFAIL) 13651923Seric return EX_TEMPFAIL; 13751362Seric } 13850581Seric 13951909Seric /* short circuit the process if no chance of a match */ 14051909Seric if (UdbSpec == NULL || UdbSpec[0] == '\0') 14151923Seric return EX_OK; 14251909Seric 14351362Seric /* if name is too long, assume it won't match */ 14451362Seric if (strlen(a->q_user) > sizeof keybuf - 12) 14551923Seric return EX_OK; 14651360Seric 14751362Seric /* if name begins with a colon, it indicates our metadata */ 14851362Seric if (a->q_user[0] == ':') 14951923Seric return EX_OK; 15051360Seric 15151362Seric /* build actual database key */ 15251362Seric (void) strcpy(keybuf, a->q_user); 15351362Seric (void) strcat(keybuf, ":maildrop"); 15451362Seric keylen = strlen(keybuf); 15551360Seric 15651360Seric breakout = FALSE; 15751362Seric for (up = UdbEnts; !breakout; up++) 15850581Seric { 15951360Seric char *user; 16050581Seric 16151360Seric /* 16251360Seric ** Select action based on entry type. 16351360Seric ** 16451360Seric ** On dropping out of this switch, "class" should 16551360Seric ** explain the type of the data, and "user" should 16651360Seric ** contain the user information. 16751360Seric */ 16850581Seric 16951360Seric switch (up->udb_type) 17051360Seric { 17151951Seric case UDB_DBFETCH: 17251362Seric key.data = keybuf; 17351362Seric key.size = keylen; 17451362Seric i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_CURSOR); 17551923Seric if (i > 0 || info.size <= 0) 17651360Seric { 17751360Seric if (tTd(28, 2)) 17858065Seric printf("udbexpand: no match on %s\n", keybuf); 17951360Seric continue; 18051360Seric } 18150581Seric 18258082Seric naddrs = 0; 18358082Seric a->q_flags &= ~QSELFREF; 18451830Seric while (i == 0 && key.size == keylen && 18551830Seric bcmp(key.data, keybuf, keylen) == 0) 18651362Seric { 18758099Seric if (bitset(EF_VRFYONLY, e->e_flags)) 18858099Seric return EX_OK; 18958099Seric 19051830Seric breakout = TRUE; 19151362Seric if (info.size < sizeof buf) 19251362Seric user = buf; 19351362Seric else 19451362Seric user = xalloc(info.size + 1); 19551362Seric bcopy(info.data, user, info.size); 19651362Seric user[info.size] = '\0'; 19750581Seric 198*58151Seric message("expanded to %s", user); 19957977Seric #ifdef LOG 20057977Seric if (LogLevel >= 10) 20157977Seric syslog(LOG_INFO, "%s: expand %s => %s", 20257977Seric e->e_id, e->e_to, user); 20357977Seric #endif 20451362Seric AliasLevel++; 20558082Seric naddrs += sendtolist(user, a, sendq, e); 20651362Seric AliasLevel--; 20751362Seric 20851362Seric if (user != buf) 20951362Seric free(user); 21051362Seric 21151362Seric /* get the next record */ 21251362Seric i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_NEXT); 21351830Seric } 21458082Seric if (naddrs > 0 && !bitset(QSELFREF, a->q_flags)) 21558065Seric { 21658065Seric if (tTd(28, 5)) 21758065Seric { 21858065Seric printf("udbexpand: QDONTSEND "); 21958065Seric printaddr(a, FALSE); 22058065Seric } 22158065Seric a->q_flags |= QDONTSEND; 22258065Seric } 22351923Seric if (i < 0) 22451923Seric { 22558010Seric syserr("udbexpand: db-get %.*s stat %d", 22658010Seric key.size, key.data, i); 22751923Seric return EX_TEMPFAIL; 22851923Seric } 22951360Seric break; 23051360Seric 23151360Seric case UDB_REMOTE: 23251741Seric /* not yet implemented */ 23351741Seric continue; 23451362Seric 23551360Seric case UDB_FORWARD: 23658099Seric if (bitset(EF_VRFYONLY, e->e_flags)) 23758099Seric return EX_OK; 23851360Seric i = strlen(up->udb_fwdhost) + strlen(a->q_user) + 1; 23951360Seric if (i < sizeof buf) 24051360Seric user = buf; 24151360Seric else 24251360Seric user = xalloc(i + 1); 24351360Seric (void) sprintf(user, "%s@%s", a->q_user, up->udb_fwdhost); 244*58151Seric message("expanded to %s", user); 24558082Seric a->q_flags &= ~QSELFREF; 24651362Seric AliasLevel++; 24758082Seric naddrs = sendtolist(user, a, sendq, e); 24851362Seric AliasLevel--; 24958082Seric if (naddrs > 0 && !bitset(QSELFREF, a->q_flags)) 25058065Seric { 25158065Seric if (tTd(28, 5)) 25258065Seric { 25358065Seric printf("udbexpand: QDONTSEND "); 25458065Seric printaddr(a, FALSE); 25558065Seric } 25658065Seric a->q_flags |= QDONTSEND; 25758065Seric } 25851362Seric if (user != buf) 25951362Seric free(user); 26051362Seric breakout = TRUE; 26151360Seric break; 26251360Seric 26351360Seric case UDB_EOLIST: 26451360Seric breakout = TRUE; 26551360Seric continue; 26651360Seric 26751360Seric default: 26851360Seric /* unknown entry type */ 26951360Seric continue; 27051360Seric } 27151362Seric } 27251923Seric return EX_OK; 27351362Seric } 27451951Seric /* 27551951Seric ** UDBSENDER -- return canonical external name of sender, given local name 27651951Seric ** 27751951Seric ** Parameters: 27851951Seric ** sender -- the name of the sender on the local machine. 27951951Seric ** 28051951Seric ** Returns: 28151951Seric ** The external name for this sender, if derivable from the 28251951Seric ** database. 28351951Seric ** NULL -- if nothing is changed from the database. 28451951Seric ** 28551951Seric ** Side Effects: 28651951Seric ** none. 28751951Seric */ 28851360Seric 28951951Seric char * 29051951Seric udbsender(sender) 29151951Seric char *sender; 29251951Seric { 29351951Seric register char *p; 29451951Seric register struct udbent *up; 29551951Seric int i; 29651951Seric int keylen; 29751951Seric DBT key, info; 29857232Seric char keybuf[MAXKEY]; 29951951Seric 30051951Seric if (tTd(28, 1)) 30151951Seric printf("udbsender(%s)\n", sender); 30251951Seric 30351951Seric if (!UdbInitialized) 30451951Seric { 30551951Seric if (_udbx_init() == EX_TEMPFAIL) 30651951Seric return NULL; 30751951Seric } 30851951Seric 30951951Seric /* short circuit if no spec */ 31051951Seric if (UdbSpec == NULL || UdbSpec[0] == '\0') 31151951Seric return NULL; 31251951Seric 31351951Seric /* long names can never match and are a pain to deal with */ 31451951Seric if (strlen(sender) > sizeof keybuf - 12) 31551951Seric return NULL; 31651951Seric 31751951Seric /* names beginning with colons indicate metadata */ 31851951Seric if (sender[0] == ':') 31951951Seric return NULL; 32051951Seric 32151951Seric /* build database key */ 32251951Seric (void) strcpy(keybuf, sender); 32351951Seric (void) strcat(keybuf, ":mailname"); 32451951Seric keylen = strlen(keybuf); 32551951Seric 32651951Seric for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) 32751951Seric { 32851951Seric /* 32951951Seric ** Select action based on entry type. 33051951Seric */ 33151951Seric 33251951Seric switch (up->udb_type) 33351951Seric { 33451951Seric case UDB_DBFETCH: 33551951Seric key.data = keybuf; 33651951Seric key.size = keylen; 33751951Seric i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0); 33851951Seric if (i != 0 || info.size <= 0) 33951951Seric { 34051951Seric if (tTd(28, 2)) 34151951Seric printf("udbsender: no match on %s\n", 34251951Seric keybuf); 34351951Seric continue; 34451951Seric } 34551951Seric 34651951Seric p = xalloc(info.size + 1); 34751951Seric bcopy(info.data, p, info.size); 34851951Seric p[info.size] = '\0'; 34951951Seric if (tTd(28, 1)) 35051951Seric printf("udbsender ==> %s\n", p); 35151951Seric return p; 35251951Seric } 35351951Seric } 35451951Seric 35551951Seric /* 35651951Seric ** Nothing yet. Search again for a default case. But only 35751951Seric ** use it if we also have a forward (:maildrop) pointer already 35851951Seric ** in the database. 35951951Seric */ 36051951Seric 36151951Seric /* build database key */ 36251951Seric (void) strcpy(keybuf, sender); 36351951Seric (void) strcat(keybuf, ":maildrop"); 36451951Seric keylen = strlen(keybuf); 36551951Seric 36651951Seric for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) 36751951Seric { 36851951Seric switch (up->udb_type) 36951951Seric { 37051951Seric case UDB_DBFETCH: 37151951Seric /* get the default case for this database */ 37251951Seric if (up->udb_default == NULL) 37351951Seric { 37451951Seric key.data = ":default:mailname"; 37551951Seric key.size = strlen(key.data); 37651951Seric i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0); 37751951Seric if (i != 0 || info.size <= 0) 37851951Seric { 37951951Seric /* no default case */ 38051951Seric up->udb_default = ""; 38151951Seric continue; 38251951Seric } 38351951Seric 38451951Seric /* save the default case */ 38551951Seric up->udb_default = xalloc(info.size + 1); 38651951Seric bcopy(info.data, up->udb_default, info.size); 38751951Seric up->udb_default[info.size] = '\0'; 38851951Seric } 38951951Seric else if (up->udb_default[0] == '\0') 39051951Seric continue; 39151951Seric 39251951Seric /* we have a default case -- verify user:maildrop */ 39351951Seric key.data = keybuf; 39451951Seric key.size = keylen; 39551951Seric i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0); 39651951Seric if (i != 0 || info.size <= 0) 39751951Seric { 39851951Seric /* nope -- no aliasing for this user */ 39951951Seric continue; 40051951Seric } 40151951Seric 40251951Seric /* they exist -- build the actual address */ 40351951Seric p = xalloc(strlen(sender) + strlen(up->udb_default) + 2); 40451951Seric (void) strcpy(p, sender); 40551951Seric (void) strcat(p, "@"); 40651951Seric (void) strcat(p, up->udb_default); 40751951Seric if (tTd(28, 1)) 40851951Seric printf("udbsender ==> %s\n", p); 40951951Seric return p; 41051951Seric } 41151951Seric } 41251951Seric 41351951Seric /* still nothing.... too bad */ 41451951Seric return NULL; 41551951Seric } 41651951Seric /* 41751951Seric ** _UDBX_INIT -- parse the UDB specification, opening any valid entries. 41851951Seric ** 41951951Seric ** Parameters: 42051951Seric ** none. 42151951Seric ** 42251951Seric ** Returns: 42351951Seric ** EX_TEMPFAIL -- if it appeared it couldn't get hold of a 42451951Seric ** database due to a host being down or some similar 42551951Seric ** (recoverable) situation. 42651951Seric ** EX_OK -- otherwise. 42751951Seric ** 42851951Seric ** Side Effects: 42951951Seric ** Fills in the UdbEnts structure from UdbSpec. 43051951Seric */ 43151951Seric 43251363Seric #define MAXUDBOPTS 27 43351363Seric 43451953Seric int 43551362Seric _udbx_init() 43651362Seric { 43751362Seric register char *p; 43851362Seric int i; 43951362Seric register struct udbent *up; 44057232Seric char buf[BUFSIZ]; 44151360Seric 44251951Seric if (UdbInitialized) 44351951Seric return EX_OK; 44451951Seric 44551908Seric # ifdef UDB_DEFAULT_SPEC 44651908Seric if (UdbSpec == NULL) 44751908Seric UdbSpec = UDB_DEFAULT_SPEC; 44851908Seric # endif 44951908Seric 45051362Seric p = UdbSpec; 45151362Seric up = UdbEnts; 45251762Seric while (p != NULL) 45351362Seric { 45451362Seric char *spec; 45551362Seric auto int rcode; 45651363Seric int nopts; 45751362Seric int nmx; 45851362Seric register struct hostent *h; 45951362Seric char *mxhosts[MAXMXHOSTS + 1]; 46051363Seric struct option opts[MAXUDBOPTS + 1]; 46151362Seric 46251362Seric while (*p == ' ' || *p == '\t' || *p == ',') 46351362Seric p++; 46451362Seric if (*p == '\0') 46551362Seric break; 46651362Seric spec = p; 46756795Seric p = strchr(p, ','); 46851761Seric if (p != NULL) 46951362Seric *p++ = '\0'; 47051363Seric 47151363Seric /* extract options */ 47251363Seric nopts = _udb_parsespec(spec, opts, MAXUDBOPTS); 47351363Seric 47451363Seric /* 47551363Seric ** Decode database specification. 47651363Seric ** 47751363Seric ** In the sendmail tradition, the leading character 47851363Seric ** defines the semantics of the rest of the entry. 47951363Seric ** 48051363Seric ** +hostname -- send a datagram to the udb server 48151363Seric ** on host "hostname" asking for the 48251363Seric ** home mail server for this user. 48351363Seric ** *hostname -- similar to +hostname, except that the 48451363Seric ** hostname is searched as an MX record; 48551363Seric ** resulting hosts are searched as for 48651363Seric ** +mxhostname. If no MX host is found, 48751363Seric ** this is the same as +hostname. 48851363Seric ** @hostname -- forward email to the indicated host. 48951363Seric ** This should be the last in the list, 49051363Seric ** since it always matches the input. 49151363Seric ** /dbname -- search the named database on the local 49251363Seric ** host using the Berkeley db package. 49351363Seric */ 49451363Seric 49551362Seric switch (*spec) 49651360Seric { 49751362Seric case '+': /* search remote database */ 49851363Seric case '*': /* search remote database (expand MX) */ 49951363Seric if (*spec == '*') 50051363Seric { 50157629Seric #ifdef NAMED_BIND 50251363Seric nmx = getmxrr(spec + 1, mxhosts, "", &rcode); 50357629Seric #else 50457629Seric mxhosts[0] = spec + 1; 50557629Seric nmx = 1; 50657629Seric rcode = 0; 50757629Seric #endif 50851363Seric if (tTd(28, 16)) 50951363Seric { 51051363Seric int i; 51151362Seric 51251363Seric printf("getmxrr(%s): %d", spec + 1, nmx); 51351363Seric for (i = 0; i <= nmx; i++) 51451363Seric printf(" %s", mxhosts[i]); 51551363Seric printf("\n"); 51651363Seric } 51751363Seric } 51851363Seric else 51951362Seric { 52051363Seric nmx = 1; 52151363Seric mxhosts[0] = spec + 1; 52251362Seric } 52351362Seric 52451362Seric for (i = 0; i < nmx; i++) 52551362Seric { 52651362Seric h = gethostbyname(mxhosts[i]); 52751362Seric if (h == NULL) 52851362Seric continue; 52951362Seric up->udb_type = UDB_REMOTE; 53051362Seric up->udb_addr.sin_family = h->h_addrtype; 53151362Seric bcopy(h->h_addr_list[0], 53251362Seric (char *) &up->udb_addr.sin_addr, 53351362Seric h->h_length); 53451362Seric up->udb_addr.sin_port = UdbPort; 53551362Seric up->udb_timeout = UdbTimeout; 53651362Seric up++; 53751362Seric } 53851362Seric 53951362Seric /* set up a datagram socket */ 54051362Seric if (UdbSock < 0) 54151362Seric { 54251362Seric UdbSock = socket(AF_INET, SOCK_DGRAM, 0); 54351362Seric (void) fcntl(UdbSock, F_SETFD, 1); 54451362Seric } 54551362Seric break; 54651362Seric 54751362Seric case '@': /* forward to remote host */ 54851362Seric up->udb_type = UDB_FORWARD; 54951362Seric up->udb_fwdhost = spec + 1; 55051362Seric up++; 55151362Seric break; 55251362Seric 55351362Seric case '/': /* look up remote name */ 55451831Seric up->udb_dbname = spec; 55551923Seric errno = 0; 55651362Seric up->udb_dbp = dbopen(spec, O_RDONLY, 0644, DB_BTREE, NULL); 55751362Seric if (up->udb_dbp == NULL) 55851923Seric { 55951923Seric if (errno != ENOENT && errno != EACCES) 56051951Seric { 56151951Seric up->udb_type = UDB_EOLIST; 56251951Seric goto tempfail; 56351951Seric } 56451362Seric break; 56551923Seric } 56651951Seric up->udb_type = UDB_DBFETCH; 56751362Seric up++; 56851362Seric break; 56951360Seric } 57051362Seric } 57151362Seric up->udb_type = UDB_EOLIST; 57251360Seric 57351362Seric if (tTd(28, 4)) 57451362Seric { 57551951Seric for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) 57651360Seric { 57751362Seric switch (up->udb_type) 57851362Seric { 57951362Seric case UDB_REMOTE: 58051362Seric printf("REMOTE: addr %s, timeo %d\n", 58151362Seric inet_ntoa(up->udb_addr.sin_addr), 58251362Seric up->udb_timeout); 58351362Seric break; 58451362Seric 58551951Seric case UDB_DBFETCH: 58651951Seric printf("FETCH: file %s\n", 58751830Seric up->udb_dbname); 58851362Seric break; 58951362Seric 59051362Seric case UDB_FORWARD: 59151362Seric printf("FORWARD: host %s\n", 59251362Seric up->udb_fwdhost); 59351362Seric break; 59451362Seric 59551362Seric default: 59651362Seric printf("UNKNOWN\n"); 59751362Seric break; 59851362Seric } 59951360Seric } 60050581Seric } 60151951Seric 60251951Seric UdbInitialized = TRUE; 60351955Seric errno = 0; 60451951Seric return EX_OK; 60551951Seric 60651951Seric /* 60751951Seric ** On temporary failure, back out anything we've already done 60851951Seric */ 60951951Seric 61051951Seric tempfail: 61151951Seric for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) 61251951Seric { 61351951Seric if (up->udb_type == UDB_DBFETCH) 61451951Seric { 61551951Seric (*up->udb_dbp->close)(up->udb_dbp); 61651951Seric } 61751951Seric } 61851951Seric return EX_TEMPFAIL; 61951360Seric } 62050581Seric 62151363Seric int 62251363Seric _udb_parsespec(udbspec, opt, maxopts) 62351363Seric char *udbspec; 62451363Seric struct option opt[]; 62551363Seric int maxopts; 62651363Seric { 62751363Seric register char *spec; 62851363Seric register char *spec_end; 62951363Seric register int optnum; 63051363Seric 63156795Seric spec_end = strchr(udbspec, ':'); 63251363Seric for (optnum = 0; optnum < maxopts && (spec = spec_end) != NULL; optnum++) 63351363Seric { 63451363Seric register char *p; 63551363Seric 63658050Seric while (isascii(*spec) && isspace(*spec)) 63751363Seric spec++; 63856795Seric spec_end = strchr(spec, ':'); 63951363Seric if (spec_end != NULL) 64051363Seric *spec_end++ = '\0'; 64151363Seric 64251363Seric opt[optnum].name = spec; 64351363Seric opt[optnum].val = NULL; 64456795Seric p = strchr(spec, '='); 64551363Seric if (p != NULL) 64651363Seric opt[optnum].val = ++p; 64751363Seric } 64851363Seric return optnum; 64951363Seric } 65051363Seric 65151360Seric #else /* not USERDB */ 65251360Seric 65351923Seric int 65455012Seric udbexpand(a, sendq, e) 65551360Seric ADDRESS *a; 65651360Seric ADDRESS **sendq; 65755012Seric ENVELOPE *e; 65851360Seric { 65951923Seric return EX_OK; 66050581Seric } 66150581Seric 66250581Seric #endif /* USERDB */ 663