1 /*
2 * Copyright (c) 2013 Gilles Chehade <gilles@poolp.org>
3 * Copyright (c) 2016 Joerg Jung <jung@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include "includes.h"
19
20 #include <sys/types.h>
21
22 #include <err.h>
23 #include <pwd.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <limits.h>
27 #include <string.h>
28 #include <unistd.h>
29
30 #include <smtpd-api.h>
31
32 static char *config;
33 static struct dict *passwd;
34
35 static int
parse_passwd_entry(int service,struct passwd * pw,char * buf)36 parse_passwd_entry(int service, struct passwd *pw, char *buf)
37 {
38 const char *e;
39 char *p;
40
41 /* username */
42 if (!(pw->pw_name = strsep(&buf, ":")) || !strlen(pw->pw_name))
43 return 0;
44
45 /* password */
46 if (!(pw->pw_passwd = strsep(&buf, ":")) ||
47 (service == K_CREDENTIALS && !strlen(pw->pw_passwd)))
48 return 0;
49
50 /* uid */
51 if (!(p = strsep(&buf, ":")))
52 return 0;
53 pw->pw_uid = strtonum(p, 0, UID_MAX, &e);
54 if (service == K_USERINFO && (!strlen(p) || e))
55 return 0;
56
57 /* gid */
58 if (!(p = strsep(&buf, ":")))
59 return 0;
60 pw->pw_gid = strtonum(p, 0, GID_MAX, &e);
61 if (service == K_USERINFO && (!strlen(p) || e))
62 return 0;
63
64 /* gecos */
65 if (!(pw->pw_gecos = strsep(&buf, ":")))
66 return 0;
67
68 /* home */
69 if (!(pw->pw_dir = strsep(&buf, ":")) ||
70 (service == K_USERINFO && !strlen(pw->pw_dir)))
71 return 0;
72
73 /* shell */
74 pw->pw_shell = strsep(&buf, ":");
75 /*
76 * explicitly allow further extra fields to support
77 * shared authentication with Dovecot Passwd-file format
78 */
79 return 1;
80 }
81
82 static int
table_passwd_update(void)83 table_passwd_update(void)
84 {
85 FILE *fp;
86 char *buf = NULL, tmp[LINE_MAX], *skip, *p;
87 size_t sz = 0;
88 ssize_t len;
89 struct passwd pw;
90 struct dict *npasswd;
91
92 /* parse configuration */
93 if ((fp = fopen(config, "r")) == NULL) {
94 log_warn("warn: \"%s\"", config);
95 return 0;
96 }
97
98 if ((npasswd = calloc(1, sizeof(*passwd))) == NULL)
99 goto err;
100
101 dict_init(npasswd);
102
103 while ((len = getline(&buf, &sz, fp)) != -1) {
104 if (buf[len - 1] == '\n')
105 buf[len - 1] = '\0';
106
107 /* skip commented entries */
108 for (skip = buf; *skip; ++skip) {
109 if (*skip == '#') {
110 *skip = '\0';
111 break;
112 }
113 }
114 /* skip empty lines */
115 if (strlen(buf) == 0)
116 continue;
117
118 if (strlcpy(tmp, buf, sizeof(tmp)) >= sizeof(tmp)) {
119 log_warnx("warn: line too long");
120 goto err;
121 }
122
123 if (!parse_passwd_entry(K_ANY, &pw, tmp)) {
124 log_warnx("warn: invalid entry");
125 goto err;
126 }
127 dict_set(npasswd, pw.pw_name, xstrdup(buf, "update"));
128 }
129 free(buf);
130 fclose(fp);
131
132 /* swap passwd table and release old one*/
133 if (passwd)
134 while (dict_poproot(passwd, (void**)&p))
135 free(p);
136 passwd = npasswd;
137
138 return 1;
139
140 err:
141 free(buf);
142 fclose(fp);
143
144 /* release passwd table */
145 if (npasswd) {
146 while (dict_poproot(npasswd, (void**)&p))
147 free(p);
148 free(npasswd);
149 }
150 return 0;
151 }
152
153 static int
table_passwd_check(int service,struct dict * params,const char * key)154 table_passwd_check(int service, struct dict *params, const char *key)
155 {
156 return -1;
157 }
158
159 static int
table_passwd_lookup(int service,struct dict * params,const char * key,char * dst,size_t sz)160 table_passwd_lookup(int service, struct dict *params, const char *key,
161 char *dst, size_t sz)
162 {
163 struct passwd pw;
164 char *line;
165 char tmp[LINE_MAX];
166
167 if ((line = dict_get(passwd, key)) == NULL)
168 return 0;
169
170 (void)strlcpy(tmp, line, sizeof(tmp));
171 if (!parse_passwd_entry(service, &pw, tmp)) {
172 log_warnx("warn: invalid entry");
173 return -1;
174 }
175
176 switch (service) {
177 case K_CREDENTIALS:
178 if (snprintf(dst, sz, "%s:%s",
179 pw.pw_name, pw.pw_passwd) >= (ssize_t)sz) {
180 log_warnx("warn: result too large");
181 return -1;
182 }
183 break;
184 case K_USERINFO:
185 if (snprintf(dst, sz, "%d:%d:%s", pw.pw_uid, pw.pw_gid,
186 pw.pw_dir) >= (ssize_t)sz) {
187 log_warnx("warn: result too large");
188 return -1;
189 }
190 break;
191 default:
192 log_warnx("warn: unknown service %d", service);
193 return -1;
194 }
195 return 1;
196 }
197
198 static int
table_passwd_fetch(int service,struct dict * params,char * dst,size_t sz)199 table_passwd_fetch(int service, struct dict *params, char *dst, size_t sz)
200 {
201 return -1;
202 }
203
204 int
main(int argc,char ** argv)205 main(int argc, char **argv)
206 {
207 int ch;
208
209 log_init(1);
210
211 while ((ch = getopt(argc, argv, "")) != -1) {
212 switch (ch) {
213 default:
214 fatalx("bad option");
215 /* NOTREACHED */
216 }
217 }
218 argc -= optind;
219 argv += optind;
220
221 if (argc != 1)
222 fatalx("bogus argument(s)");
223
224 config = argv[0];
225
226 if (table_passwd_update() == 0)
227 fatalx("error parsing config file");
228
229 table_api_on_update(table_passwd_update);
230 table_api_on_check(table_passwd_check);
231 table_api_on_lookup(table_passwd_lookup);
232 table_api_on_fetch(table_passwd_fetch);
233 table_api_dispatch();
234
235 return 0;
236 }
237