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*59615Seric static char sccsid [] = "@(#)udb.c 6.18 (Berkeley) 05/01/93 (with USERDB)"; 1251360Seric #else 13*59615Seric static char sccsid [] = "@(#)udb.c 6.18 (Berkeley) 05/01/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 <netdb.h> 2450581Seric #include <db.h> 2550581Seric 2650581Seric /* 2753654Seric ** UDB.C -- interface between sendmail and Berkeley User Data Base. 2850581Seric ** 2951363Seric ** This depends on the 4.4BSD db package. 3050581Seric */ 3150581Seric 3251362Seric 3351360Seric struct udbent 3451360Seric { 3551360Seric char *udb_spec; /* string version of spec */ 3651360Seric int udb_type; /* type of entry */ 3751951Seric char *udb_default; /* default host for outgoing mail */ 3851360Seric union 3951360Seric { 4051360Seric /* type UE_REMOTE -- do remote call for lookup */ 4151360Seric struct 4251360Seric { 4351360Seric struct sockaddr_in _udb_addr; /* address */ 4451360Seric int _udb_timeout; /* timeout */ 4551360Seric } udb_remote; 4651360Seric #define udb_addr udb_u.udb_remote._udb_addr 4751360Seric #define udb_timeout udb_u.udb_remote._udb_timeout 4851360Seric 4951360Seric /* type UE_FORWARD -- forward message to remote */ 5051360Seric struct 5151360Seric { 5251360Seric char *_udb_fwdhost; /* name of forward host */ 5351360Seric } udb_forward; 5451360Seric #define udb_fwdhost udb_u.udb_forward._udb_fwdhost 5551360Seric 5651951Seric /* type UE_FETCH -- lookup in local database */ 5751360Seric struct 5851360Seric { 5951360Seric char *_udb_dbname; /* pathname of database */ 6051360Seric DB *_udb_dbp; /* open database ptr */ 6151360Seric } udb_lookup; 6251360Seric #define udb_dbname udb_u.udb_lookup._udb_dbname 6351360Seric #define udb_dbp udb_u.udb_lookup._udb_dbp 6451360Seric } udb_u; 6551360Seric }; 6651360Seric 6751360Seric #define UDB_EOLIST 0 /* end of list */ 6851360Seric #define UDB_SKIP 1 /* skip this entry */ 6951360Seric #define UDB_REMOTE 2 /* look up in remote database */ 7051951Seric #define UDB_DBFETCH 3 /* look up in local database */ 7151360Seric #define UDB_FORWARD 4 /* forward to remote host */ 7251360Seric 7351360Seric #define MAXUDBENT 10 /* maximum number of UDB entries */ 7451360Seric 7551363Seric 7651363Seric struct option 7751363Seric { 7851363Seric char *name; 7951363Seric char *val; 8051363Seric }; 8151363Seric /* 8251363Seric ** UDBEXPAND -- look up user in database and expand 8351363Seric ** 8451363Seric ** Parameters: 8551363Seric ** a -- address to expand. 8651363Seric ** sendq -- pointer to head of sendq to put the expansions in. 8751363Seric ** 8851363Seric ** Returns: 8951923Seric ** EX_TEMPFAIL -- if something "odd" happened -- probably due 9051923Seric ** to accessing a file on an NFS server that is down. 9151923Seric ** EX_OK -- otherwise. 9251363Seric ** 9351363Seric ** Side Effects: 9451363Seric ** Modifies sendq. 9551363Seric */ 9651363Seric 9751363Seric int UdbPort = 1616; 9851363Seric int UdbTimeout = 10; 9951363Seric 10051953Seric struct udbent UdbEnts[MAXUDBENT + 1]; 10151953Seric int UdbSock = -1; 10251953Seric bool UdbInitialized = FALSE; 10351360Seric 10451923Seric int 10555012Seric udbexpand(a, sendq, e) 10650581Seric register ADDRESS *a; 10750581Seric ADDRESS **sendq; 10855012Seric register ENVELOPE *e; 10950581Seric { 11050581Seric int i; 11150581Seric register char *p; 11250581Seric DBT key; 11350581Seric DBT info; 11451360Seric bool breakout; 11551360Seric register struct udbent *up; 11651362Seric int keylen; 11758082Seric int naddrs; 11857232Seric char keybuf[MAXKEY]; 11957232Seric char buf[BUFSIZ]; 12050581Seric 12150581Seric if (tTd(28, 1)) 12258065Seric printf("udbexpand(%s)\n", a->q_paddr); 12350581Seric 12450581Seric /* make certain we are supposed to send to this address */ 12558154Seric if (bitset(QDONTSEND|QVERIFIED, a->q_flags)) 12651923Seric return EX_OK; 12755012Seric e->e_to = a->q_paddr; 12850581Seric 12951360Seric /* on first call, locate the database */ 13051951Seric if (!UdbInitialized) 13150581Seric { 13251923Seric extern int _udbx_init(); 13351362Seric 13451923Seric if (_udbx_init() == EX_TEMPFAIL) 13551923Seric return EX_TEMPFAIL; 13651362Seric } 13750581Seric 13851909Seric /* short circuit the process if no chance of a match */ 13951909Seric if (UdbSpec == NULL || UdbSpec[0] == '\0') 14051923Seric return EX_OK; 14151909Seric 14251362Seric /* if name is too long, assume it won't match */ 14351362Seric if (strlen(a->q_user) > sizeof keybuf - 12) 14451923Seric return EX_OK; 14551360Seric 14651362Seric /* if name begins with a colon, it indicates our metadata */ 14751362Seric if (a->q_user[0] == ':') 14851923Seric return EX_OK; 14951360Seric 15051362Seric /* build actual database key */ 15151362Seric (void) strcpy(keybuf, a->q_user); 15251362Seric (void) strcat(keybuf, ":maildrop"); 15351362Seric keylen = strlen(keybuf); 15451360Seric 15551360Seric breakout = FALSE; 15651362Seric for (up = UdbEnts; !breakout; up++) 15750581Seric { 15851360Seric char *user; 15950581Seric 16051360Seric /* 16151360Seric ** Select action based on entry type. 16251360Seric ** 16351360Seric ** On dropping out of this switch, "class" should 16451360Seric ** explain the type of the data, and "user" should 16551360Seric ** contain the user information. 16651360Seric */ 16750581Seric 16851360Seric switch (up->udb_type) 16951360Seric { 17051951Seric case UDB_DBFETCH: 17151362Seric key.data = keybuf; 17251362Seric key.size = keylen; 17351362Seric i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_CURSOR); 17451923Seric if (i > 0 || info.size <= 0) 17551360Seric { 17651360Seric if (tTd(28, 2)) 17758065Seric printf("udbexpand: no match on %s\n", keybuf); 17851360Seric continue; 17951360Seric } 18050581Seric 18158082Seric naddrs = 0; 18258082Seric a->q_flags &= ~QSELFREF; 18351830Seric while (i == 0 && key.size == keylen && 18451830Seric bcmp(key.data, keybuf, keylen) == 0) 18551362Seric { 18658099Seric if (bitset(EF_VRFYONLY, e->e_flags)) 18758154Seric { 18858154Seric a->q_flags |= QVERIFIED; 18958884Seric e->e_nrcpts++; 19058099Seric return EX_OK; 19158154Seric } 19258099Seric 19351830Seric breakout = TRUE; 19451362Seric if (info.size < sizeof buf) 19551362Seric user = buf; 19651362Seric else 19751362Seric user = xalloc(info.size + 1); 19851362Seric bcopy(info.data, user, info.size); 19951362Seric user[info.size] = '\0'; 20050581Seric 20158151Seric message("expanded to %s", user); 20257977Seric #ifdef LOG 20357977Seric if (LogLevel >= 10) 20457977Seric syslog(LOG_INFO, "%s: expand %s => %s", 20557977Seric e->e_id, e->e_to, user); 20657977Seric #endif 20751362Seric AliasLevel++; 20858082Seric naddrs += sendtolist(user, a, sendq, e); 20951362Seric AliasLevel--; 21051362Seric 21151362Seric if (user != buf) 21251362Seric free(user); 21351362Seric 21451362Seric /* get the next record */ 21551362Seric i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_NEXT); 21651830Seric } 21758082Seric if (naddrs > 0 && !bitset(QSELFREF, a->q_flags)) 21858065Seric { 21958065Seric if (tTd(28, 5)) 22058065Seric { 22158065Seric printf("udbexpand: QDONTSEND "); 22258065Seric printaddr(a, FALSE); 22358065Seric } 22458065Seric a->q_flags |= QDONTSEND; 22558065Seric } 22651923Seric if (i < 0) 22751923Seric { 22858010Seric syserr("udbexpand: db-get %.*s stat %d", 22958010Seric key.size, key.data, i); 23051923Seric return EX_TEMPFAIL; 23151923Seric } 23251360Seric break; 23351360Seric 23451360Seric case UDB_REMOTE: 23551741Seric /* not yet implemented */ 23651741Seric continue; 23751362Seric 23851360Seric case UDB_FORWARD: 23958099Seric if (bitset(EF_VRFYONLY, e->e_flags)) 24058099Seric return EX_OK; 24151360Seric i = strlen(up->udb_fwdhost) + strlen(a->q_user) + 1; 24251360Seric if (i < sizeof buf) 24351360Seric user = buf; 24451360Seric else 24551360Seric user = xalloc(i + 1); 24651360Seric (void) sprintf(user, "%s@%s", a->q_user, up->udb_fwdhost); 24758151Seric message("expanded to %s", user); 24858082Seric a->q_flags &= ~QSELFREF; 24951362Seric AliasLevel++; 25058082Seric naddrs = sendtolist(user, a, sendq, e); 25151362Seric AliasLevel--; 25258082Seric if (naddrs > 0 && !bitset(QSELFREF, a->q_flags)) 25358065Seric { 25458065Seric if (tTd(28, 5)) 25558065Seric { 25658065Seric printf("udbexpand: QDONTSEND "); 25758065Seric printaddr(a, FALSE); 25858065Seric } 25958065Seric a->q_flags |= QDONTSEND; 26058065Seric } 26151362Seric if (user != buf) 26251362Seric free(user); 26351362Seric breakout = TRUE; 26451360Seric break; 26551360Seric 26651360Seric case UDB_EOLIST: 26751360Seric breakout = TRUE; 26851360Seric continue; 26951360Seric 27051360Seric default: 27151360Seric /* unknown entry type */ 27251360Seric continue; 27351360Seric } 27451362Seric } 27551923Seric return EX_OK; 27651362Seric } 27751951Seric /* 27851951Seric ** UDBSENDER -- return canonical external name of sender, given local name 27951951Seric ** 28051951Seric ** Parameters: 28151951Seric ** sender -- the name of the sender on the local machine. 28251951Seric ** 28351951Seric ** Returns: 28451951Seric ** The external name for this sender, if derivable from the 28551951Seric ** database. 28651951Seric ** NULL -- if nothing is changed from the database. 28751951Seric ** 28851951Seric ** Side Effects: 28951951Seric ** none. 29051951Seric */ 29151360Seric 29251951Seric char * 29351951Seric udbsender(sender) 29451951Seric char *sender; 29551951Seric { 29651951Seric register char *p; 29751951Seric register struct udbent *up; 29851951Seric int i; 29951951Seric int keylen; 30051951Seric DBT key, info; 30157232Seric char keybuf[MAXKEY]; 30251951Seric 30351951Seric if (tTd(28, 1)) 30451951Seric printf("udbsender(%s)\n", sender); 30551951Seric 30651951Seric if (!UdbInitialized) 30751951Seric { 30851951Seric if (_udbx_init() == EX_TEMPFAIL) 30951951Seric return NULL; 31051951Seric } 31151951Seric 31251951Seric /* short circuit if no spec */ 31351951Seric if (UdbSpec == NULL || UdbSpec[0] == '\0') 31451951Seric return NULL; 31551951Seric 31651951Seric /* long names can never match and are a pain to deal with */ 31751951Seric if (strlen(sender) > sizeof keybuf - 12) 31851951Seric return NULL; 31951951Seric 32051951Seric /* names beginning with colons indicate metadata */ 32151951Seric if (sender[0] == ':') 32251951Seric return NULL; 32351951Seric 32451951Seric /* build database key */ 32551951Seric (void) strcpy(keybuf, sender); 32651951Seric (void) strcat(keybuf, ":mailname"); 32751951Seric keylen = strlen(keybuf); 32851951Seric 32951951Seric for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) 33051951Seric { 33151951Seric /* 33251951Seric ** Select action based on entry type. 33351951Seric */ 33451951Seric 33551951Seric switch (up->udb_type) 33651951Seric { 33751951Seric case UDB_DBFETCH: 33851951Seric key.data = keybuf; 33951951Seric key.size = keylen; 34051951Seric i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0); 34151951Seric if (i != 0 || info.size <= 0) 34251951Seric { 34351951Seric if (tTd(28, 2)) 34451951Seric printf("udbsender: no match on %s\n", 34551951Seric keybuf); 34651951Seric continue; 34751951Seric } 34851951Seric 34951951Seric p = xalloc(info.size + 1); 35051951Seric bcopy(info.data, p, info.size); 35151951Seric p[info.size] = '\0'; 35251951Seric if (tTd(28, 1)) 35351951Seric printf("udbsender ==> %s\n", p); 35451951Seric return p; 35551951Seric } 35651951Seric } 35751951Seric 35851951Seric /* 35951951Seric ** Nothing yet. Search again for a default case. But only 36051951Seric ** use it if we also have a forward (:maildrop) pointer already 36151951Seric ** in the database. 36251951Seric */ 36351951Seric 36451951Seric /* build database key */ 36551951Seric (void) strcpy(keybuf, sender); 36651951Seric (void) strcat(keybuf, ":maildrop"); 36751951Seric keylen = strlen(keybuf); 36851951Seric 36951951Seric for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) 37051951Seric { 37151951Seric switch (up->udb_type) 37251951Seric { 37351951Seric case UDB_DBFETCH: 37451951Seric /* get the default case for this database */ 37551951Seric if (up->udb_default == NULL) 37651951Seric { 37751951Seric key.data = ":default:mailname"; 37851951Seric key.size = strlen(key.data); 37951951Seric i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0); 38051951Seric if (i != 0 || info.size <= 0) 38151951Seric { 38251951Seric /* no default case */ 38351951Seric up->udb_default = ""; 38451951Seric continue; 38551951Seric } 38651951Seric 38751951Seric /* save the default case */ 38851951Seric up->udb_default = xalloc(info.size + 1); 38951951Seric bcopy(info.data, up->udb_default, info.size); 39051951Seric up->udb_default[info.size] = '\0'; 39151951Seric } 39251951Seric else if (up->udb_default[0] == '\0') 39351951Seric continue; 39451951Seric 39551951Seric /* we have a default case -- verify user:maildrop */ 39651951Seric key.data = keybuf; 39751951Seric key.size = keylen; 39851951Seric i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0); 39951951Seric if (i != 0 || info.size <= 0) 40051951Seric { 40151951Seric /* nope -- no aliasing for this user */ 40251951Seric continue; 40351951Seric } 40451951Seric 40551951Seric /* they exist -- build the actual address */ 40651951Seric p = xalloc(strlen(sender) + strlen(up->udb_default) + 2); 40751951Seric (void) strcpy(p, sender); 40851951Seric (void) strcat(p, "@"); 40951951Seric (void) strcat(p, up->udb_default); 41051951Seric if (tTd(28, 1)) 41151951Seric printf("udbsender ==> %s\n", p); 41251951Seric return p; 41351951Seric } 41451951Seric } 41551951Seric 41651951Seric /* still nothing.... too bad */ 41751951Seric return NULL; 41851951Seric } 41951951Seric /* 42051951Seric ** _UDBX_INIT -- parse the UDB specification, opening any valid entries. 42151951Seric ** 42251951Seric ** Parameters: 42351951Seric ** none. 42451951Seric ** 42551951Seric ** Returns: 42651951Seric ** EX_TEMPFAIL -- if it appeared it couldn't get hold of a 42751951Seric ** database due to a host being down or some similar 42851951Seric ** (recoverable) situation. 42951951Seric ** EX_OK -- otherwise. 43051951Seric ** 43151951Seric ** Side Effects: 43251951Seric ** Fills in the UdbEnts structure from UdbSpec. 43351951Seric */ 43451951Seric 43551363Seric #define MAXUDBOPTS 27 43651363Seric 43751953Seric int 43851362Seric _udbx_init() 43951362Seric { 44051362Seric register char *p; 44151362Seric int i; 44251362Seric register struct udbent *up; 44357232Seric char buf[BUFSIZ]; 44451360Seric 44551951Seric if (UdbInitialized) 44651951Seric return EX_OK; 44751951Seric 44851908Seric # ifdef UDB_DEFAULT_SPEC 44951908Seric if (UdbSpec == NULL) 45051908Seric UdbSpec = UDB_DEFAULT_SPEC; 45151908Seric # endif 45251908Seric 45351362Seric p = UdbSpec; 45451362Seric up = UdbEnts; 45551762Seric while (p != NULL) 45651362Seric { 45751362Seric char *spec; 45851362Seric auto int rcode; 45951363Seric int nopts; 46051362Seric int nmx; 46151362Seric register struct hostent *h; 46251362Seric char *mxhosts[MAXMXHOSTS + 1]; 46351363Seric struct option opts[MAXUDBOPTS + 1]; 46451362Seric 46551362Seric while (*p == ' ' || *p == '\t' || *p == ',') 46651362Seric p++; 46751362Seric if (*p == '\0') 46851362Seric break; 46951362Seric spec = p; 47056795Seric p = strchr(p, ','); 47151761Seric if (p != NULL) 47251362Seric *p++ = '\0'; 47351363Seric 47451363Seric /* extract options */ 47551363Seric nopts = _udb_parsespec(spec, opts, MAXUDBOPTS); 47651363Seric 47751363Seric /* 47851363Seric ** Decode database specification. 47951363Seric ** 48051363Seric ** In the sendmail tradition, the leading character 48151363Seric ** defines the semantics of the rest of the entry. 48251363Seric ** 48351363Seric ** +hostname -- send a datagram to the udb server 48451363Seric ** on host "hostname" asking for the 48551363Seric ** home mail server for this user. 48651363Seric ** *hostname -- similar to +hostname, except that the 48751363Seric ** hostname is searched as an MX record; 48851363Seric ** resulting hosts are searched as for 48951363Seric ** +mxhostname. If no MX host is found, 49051363Seric ** this is the same as +hostname. 49151363Seric ** @hostname -- forward email to the indicated host. 49251363Seric ** This should be the last in the list, 49351363Seric ** since it always matches the input. 49451363Seric ** /dbname -- search the named database on the local 49551363Seric ** host using the Berkeley db package. 49651363Seric */ 49751363Seric 49851362Seric switch (*spec) 49951360Seric { 50051362Seric case '+': /* search remote database */ 50151363Seric case '*': /* search remote database (expand MX) */ 50251363Seric if (*spec == '*') 50351363Seric { 50457629Seric #ifdef NAMED_BIND 50559273Seric nmx = getmxrr(spec + 1, mxhosts, FALSE, &rcode); 50657629Seric #else 50757629Seric mxhosts[0] = spec + 1; 50857629Seric nmx = 1; 50957629Seric rcode = 0; 51057629Seric #endif 51151363Seric if (tTd(28, 16)) 51251363Seric { 51351363Seric int i; 51451362Seric 51551363Seric printf("getmxrr(%s): %d", spec + 1, nmx); 51651363Seric for (i = 0; i <= nmx; i++) 51751363Seric printf(" %s", mxhosts[i]); 51851363Seric printf("\n"); 51951363Seric } 52051363Seric } 52151363Seric else 52251362Seric { 52351363Seric nmx = 1; 52451363Seric mxhosts[0] = spec + 1; 52551362Seric } 52651362Seric 52751362Seric for (i = 0; i < nmx; i++) 52851362Seric { 52951362Seric h = gethostbyname(mxhosts[i]); 53051362Seric if (h == NULL) 53151362Seric continue; 53251362Seric up->udb_type = UDB_REMOTE; 53351362Seric up->udb_addr.sin_family = h->h_addrtype; 53451362Seric bcopy(h->h_addr_list[0], 53551362Seric (char *) &up->udb_addr.sin_addr, 53651362Seric h->h_length); 53751362Seric up->udb_addr.sin_port = UdbPort; 53851362Seric up->udb_timeout = UdbTimeout; 53951362Seric up++; 54051362Seric } 54151362Seric 54251362Seric /* set up a datagram socket */ 54351362Seric if (UdbSock < 0) 54451362Seric { 54551362Seric UdbSock = socket(AF_INET, SOCK_DGRAM, 0); 54651362Seric (void) fcntl(UdbSock, F_SETFD, 1); 54751362Seric } 54851362Seric break; 54951362Seric 55051362Seric case '@': /* forward to remote host */ 55151362Seric up->udb_type = UDB_FORWARD; 55251362Seric up->udb_fwdhost = spec + 1; 55351362Seric up++; 55451362Seric break; 55551362Seric 55651362Seric case '/': /* look up remote name */ 55751831Seric up->udb_dbname = spec; 55851923Seric errno = 0; 55951362Seric up->udb_dbp = dbopen(spec, O_RDONLY, 0644, DB_BTREE, NULL); 56051362Seric if (up->udb_dbp == NULL) 56151923Seric { 56251923Seric if (errno != ENOENT && errno != EACCES) 56351951Seric { 564*59615Seric #ifdef LOG 565*59615Seric if (LogLevel > 2) 566*59615Seric syslog(LOG_ERR, "dbopen(%s): %e", 567*59615Seric spec); 568*59615Seric #endif 56951951Seric up->udb_type = UDB_EOLIST; 57051951Seric goto tempfail; 57151951Seric } 57251362Seric break; 57351923Seric } 57451951Seric up->udb_type = UDB_DBFETCH; 57551362Seric up++; 57651362Seric break; 57751360Seric } 57851362Seric } 57951362Seric up->udb_type = UDB_EOLIST; 58051360Seric 58151362Seric if (tTd(28, 4)) 58251362Seric { 58351951Seric for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) 58451360Seric { 58551362Seric switch (up->udb_type) 58651362Seric { 58751362Seric case UDB_REMOTE: 58851362Seric printf("REMOTE: addr %s, timeo %d\n", 58958755Seric anynet_ntoa(&up->udb_addr), 59051362Seric up->udb_timeout); 59151362Seric break; 59251362Seric 59351951Seric case UDB_DBFETCH: 59451951Seric printf("FETCH: file %s\n", 59551830Seric up->udb_dbname); 59651362Seric break; 59751362Seric 59851362Seric case UDB_FORWARD: 59951362Seric printf("FORWARD: host %s\n", 60051362Seric up->udb_fwdhost); 60151362Seric break; 60251362Seric 60351362Seric default: 60451362Seric printf("UNKNOWN\n"); 60551362Seric break; 60651362Seric } 60751360Seric } 60850581Seric } 60951951Seric 61051951Seric UdbInitialized = TRUE; 61151955Seric errno = 0; 61251951Seric return EX_OK; 61351951Seric 61451951Seric /* 61551951Seric ** On temporary failure, back out anything we've already done 61651951Seric */ 61751951Seric 61851951Seric tempfail: 61951951Seric for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) 62051951Seric { 62151951Seric if (up->udb_type == UDB_DBFETCH) 62251951Seric { 62351951Seric (*up->udb_dbp->close)(up->udb_dbp); 62451951Seric } 62551951Seric } 62651951Seric return EX_TEMPFAIL; 62751360Seric } 62850581Seric 62951363Seric int 63051363Seric _udb_parsespec(udbspec, opt, maxopts) 63151363Seric char *udbspec; 63251363Seric struct option opt[]; 63351363Seric int maxopts; 63451363Seric { 63551363Seric register char *spec; 63651363Seric register char *spec_end; 63751363Seric register int optnum; 63851363Seric 63956795Seric spec_end = strchr(udbspec, ':'); 64051363Seric for (optnum = 0; optnum < maxopts && (spec = spec_end) != NULL; optnum++) 64151363Seric { 64251363Seric register char *p; 64351363Seric 64458050Seric while (isascii(*spec) && isspace(*spec)) 64551363Seric spec++; 64656795Seric spec_end = strchr(spec, ':'); 64751363Seric if (spec_end != NULL) 64851363Seric *spec_end++ = '\0'; 64951363Seric 65051363Seric opt[optnum].name = spec; 65151363Seric opt[optnum].val = NULL; 65256795Seric p = strchr(spec, '='); 65351363Seric if (p != NULL) 65451363Seric opt[optnum].val = ++p; 65551363Seric } 65651363Seric return optnum; 65751363Seric } 65851363Seric 65951360Seric #else /* not USERDB */ 66051360Seric 66151923Seric int 66255012Seric udbexpand(a, sendq, e) 66351360Seric ADDRESS *a; 66451360Seric ADDRESS **sendq; 66555012Seric ENVELOPE *e; 66651360Seric { 66751923Seric return EX_OK; 66850581Seric } 66950581Seric 67050581Seric #endif /* USERDB */ 671