1 /*
2    This program is free software; you can redistribute it and/or
3    modify it under the terms of the GNU General Public License as
4    published by the Free Software Foundation; either version 2, or (at
5    your option) any later version.
6 
7    This program is distributed in the hope that it will be useful, but
8    WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10    General Public License for more details.
11 
12    Copyright (c) Alexey Mahotkin <alexm@hsys.msk.ru> 2002-2004
13 
14 */
15 
16 #include <config.h>
17 
18 #include "logging.h"
19 #include "pam-support.h"
20 
21 #include <errno.h>
22 #include <getopt.h>
23 #include <grp.h>
24 #include <pwd.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <unistd.h>
30 
31 /* command line options processing */
32 int opt_debugging = 0;
33 static int opt_dont_set_env = 0;
34 static int opt_dont_chdir_home = 0;
35 int opt_use_stdout = 0;
36 
37 static const char* short_options = "dehs:HV";
38 
39 enum { OPT_STDOUT = 1 };
40 static struct option long_options[] = {
41     { "debug", no_argument, NULL, 'd' },
42     { "help", no_argument, NULL, 'h' },
43     { "noenv", no_argument, NULL, 'e' },
44     { "no-chdir-home", no_argument, NULL, 'H' },
45     { "service", required_argument, NULL, 's' },
46     { "stdout", no_argument, NULL, OPT_STDOUT },
47     { "version", no_argument, NULL, 'V' },
48     { NULL, 0, NULL, 0 }
49 };
50 
51 static const char* usage =
52 "Usage: " PACKAGE " [OPTION]... -- prog...\n"
53 "\n"
54 "Authenticate using PAM and the checkpassword protocol:\n"
55 "\t<URL:http://cr.yp.to/checkpwd/interface.html>\n"
56 "and run the program specified as 'prog'\n"
57 "\n"
58 "Options are:\n"
59 "  -d, --debug\t\tturn on debugging output\n"
60 "  -e, --noenv\t\tdo not set uid, gid, environment variables, \n\t\t\tand home directory\n"
61 "  -H, --no-chdir-home\tdo not change to home directory\n"
62 "  -h, --help\t\tdisplay this help and exit\n"
63 "  -s, --service=SERVICE\tspecify PAM service name to use\n"
64 "\t\t\t(by default use the contents of $PAM_SERVICE)\n"
65 "  -V, --version\t\tdisplay version information and exit\n";
66 
67 
68 /* checkpassword protocol support */
69 #define PROTOCOL_FD 3
70 #define PROTOCOL_LEN 512
71 static char up[PROTOCOL_LEN];
72 
73 /* pointers into up[] */
74 static char* username = NULL;
75 static char* password = NULL;
76 
77 int
main(int argc,char * argv[])78 main (int argc, char *argv[])
79 {
80     FILE* protocol;
81     int i, uplen;
82     struct passwd* pw;
83     char* service_name = NULL;
84     int exit_status = 1;
85 
86     init_logging(argv[0]);
87 
88     /* process command line options */
89     opterr = 0;
90     while (1) {
91 	int option_index = 0;
92 	int c = getopt_long (argc, argv, short_options, long_options, &option_index);
93 
94 	if (c == -1)
95 	    break;
96 
97 	switch (c) {
98 	case OPT_STDOUT:
99 	    opt_use_stdout = 1;
100 	    break;
101 
102 	case 'd':
103 	    opt_debugging = 1;
104 	    break;
105 
106 	case 'e':
107 	    opt_dont_set_env = 1;
108 	    break;
109 
110 	case 'H':
111 	    opt_dont_chdir_home = 1;
112 	    break;
113 
114 	case 'h':
115 	    puts(usage);
116 	    exit(0);
117 
118 	case 's':
119 	    service_name = strdup(optarg);
120 	    if (!service_name) {
121 		fatal("Out of memory");
122 		exit(1);
123 	    }
124 	    break;
125 
126 	case 'V':
127 	    puts(PACKAGE " " VERSION);
128 	    exit(0);
129 
130 	case '?':
131 	    fatal("Invalid command line, see --help");
132 	    exit(2);
133 	}
134     }
135 
136     if (service_name == NULL) {
137 	char *envval = getenv("PAM_SERVICE");
138 	if (!envval) {
139 	    fatal("PAM service name not specified");
140 	    exit_status = 2;
141 	    goto out;
142 	}
143 	service_name = strdup(envval);
144 	if (!service_name) {
145 
146 	    fatal("Out of memory");
147 	    exit_status = 111;
148 	    goto out;
149 	}
150     }
151 
152     terminate_logging();
153     init_logging(service_name);
154 
155     /* read the username/password */
156     protocol = fdopen(PROTOCOL_FD, "r");
157     if (protocol == NULL) {
158 	fatal("Error opening fd %d: %s", PROTOCOL_FD, strerror(errno));
159 	exit_status = 2;
160 	goto out;
161     }
162     debugging("Reading username and password");
163     uplen = fread(up, 1, PROTOCOL_LEN, protocol);
164     if (uplen == 0) {
165 	fatal("Checkpassword protocol failure: zero bytes read");
166 	exit_status = 2;
167 	goto out;
168     }
169     i = 0;
170     /* extract username */
171     username = up + i;
172     while (up[i++]) {
173 	if (i >= uplen) {
174 	    fatal("Checkpassword protocol failure: username not provided");
175 	    exit_status = 2;
176 	    goto out;
177 	}
178     }
179     debugging("Username '%s'", username);
180 
181     /* extract password */
182     password = up + i;
183     while (up[i++]) {
184 	if (i >= uplen) {
185 	    fatal("Checkpassword protocol failure: password not provided");
186 	    exit_status = 2;
187 	    goto out;
188 	}
189     }
190     debugging("Password read successfully");
191 
192     /* authenticate using PAM */
193     exit_status = authenticate_using_pam(service_name, username, password);
194     if (exit_status != 0)
195 	goto out;
196 
197     if (opt_dont_set_env)
198       goto execute_program; /* skip setting up process environment */
199 
200     /* switch to proper uid/gid/groups */
201     pw = getpwnam(username);
202     if (!pw) {
203 	if (opt_debugging)
204 	    fatal("Error getting information about %s from /etc/passwd: %s", username, strerror(errno));
205 	exit_status = 2;
206 	goto out;
207     }
208 
209     /* set supplementary groups */
210     if (initgroups(username, pw->pw_gid) == -1) {
211 	fatal("Error setting supplementary groups for user %s: %s", username, strerror(errno));
212 	exit_status = 111;
213 	goto out;
214     }
215 
216     /* set gid */
217     if (setgid(pw->pw_gid) == -1) {
218 	fatal("setgid(%d) error: %s", pw->pw_gid, strerror(errno));
219 	exit_status = 111;
220 	goto out;
221     }
222 
223     /* set uid */
224     if (setuid(pw->pw_uid) == -1) {
225 	fatal("setuid(%d) error: %s", pw->pw_uid, strerror(errno));
226 	exit_status = 111;
227 	goto out;
228     }
229 
230     if (!opt_dont_chdir_home) {
231 	/* switch to user home directory */
232 	if (chdir(pw->pw_dir) == -1) {
233 	    fatal("Error changing directory %s: %s", pw->pw_dir, strerror(errno));
234 	    exit_status = 1;
235 	    goto out;
236 	}
237     }
238 
239     /* set $USER */
240     if (setenv("USER", username, 1) == -1) {
241 	fatal("Error setting $USER to %s: %s", username, strerror(errno));
242 	exit_status = 111;
243 	goto out;
244     }
245 
246     /* set $HOME */
247     if (setenv("HOME", pw->pw_dir, 1) == -1) {
248 	fatal("Error setting $HOME to %s: %s", pw->pw_dir, strerror(errno));
249 	exit_status = 111;
250 	goto out;
251     }
252 
253     /* set $SHELL */
254     if (setenv("SHELL", pw->pw_shell, 1) == -1) {
255 	fatal("Error setting $SHELL to %s: %s", pw->pw_shell, strerror(errno));
256 	exit_status = 111;
257 	goto out;
258     }
259 
260  execute_program:
261 
262     /* execute the program, if any */
263     if (optind < argc) {
264 	debugging("Executing %s", argv[optind]);
265 
266 	execvp(argv[optind], argv + optind);
267 	fatal("Cannot exec(%s): %s\n", argv[optind], strerror(errno));
268 	exit_status = 2;
269 	goto out;
270     }
271     /* if no program was provided in command line, simply exit */
272 
273  out:
274     memset(up, 0x00, sizeof(up));
275 
276     debugging("Exiting with status %d", exit_status);
277 
278     terminate_logging();
279 
280     exit(exit_status);
281 }
282