1 |
/* |
2 |
* mailauth.c |
3 |
* |
4 |
* An example program for CERBERUS. |
5 |
* ( http://sourceforge.jp/projects/tomoyo/document/winf2005-en.pdf ) |
6 |
* |
7 |
* Copyright (C) 2005-2008 NTT DATA CORPORATION |
8 |
* |
9 |
* Version: 1.6.5-pre 2008/10/23 |
10 |
*/ |
11 |
#define _GNU_SOURCE |
12 |
#include <stdio.h> |
13 |
#include <unistd.h> |
14 |
#include <sys/time.h> |
15 |
#include <string.h> |
16 |
#include <stdlib.h> |
17 |
#include <termios.h> |
18 |
#include <sys/types.h> |
19 |
#include <sys/stat.h> |
20 |
#include <fcntl.h> |
21 |
#include <sys/socket.h> |
22 |
#include <sys/wait.h> |
23 |
#include <time.h> |
24 |
|
25 |
static int str_starts(char *src, const char *find) |
26 |
{ |
27 |
const int len = strlen(find); |
28 |
if (strncmp(src, find, len)) |
29 |
return 0; |
30 |
memmove(src, src + len, strlen(src + len) + 1); |
31 |
return 1; |
32 |
} |
33 |
|
34 |
static char *str_dup(const char *str) |
35 |
{ |
36 |
char *cp = strdup(str); |
37 |
if (!cp) { |
38 |
fprintf(stderr, "Out of memory\n"); |
39 |
exit(1); |
40 |
} |
41 |
return cp; |
42 |
} |
43 |
|
44 |
static void unescape_line(unsigned char *buffer) |
45 |
{ |
46 |
unsigned char *cp = buffer; |
47 |
unsigned char c; |
48 |
unsigned char d; |
49 |
unsigned char e; |
50 |
if (!cp) |
51 |
return; |
52 |
while (1) { |
53 |
c = *buffer++; |
54 |
if (!c) |
55 |
break; |
56 |
if (c != '\\') { |
57 |
*cp++ = c; |
58 |
continue; |
59 |
} |
60 |
c = *buffer++; |
61 |
if (c == '\\') { |
62 |
*cp++ = c; |
63 |
continue; |
64 |
} |
65 |
if (c < '0' || c > '3') |
66 |
break; |
67 |
d = *buffer++; |
68 |
if (d < '0' || d > '7') |
69 |
break; |
70 |
e = *buffer++; |
71 |
if (e < '0' || e > '7') |
72 |
break; |
73 |
*cp++ = ((c - '0') << 6) + ((d - '0') << 3) + (e - '0'); |
74 |
} |
75 |
*cp = '\0'; |
76 |
} |
77 |
|
78 |
static void normalize_line(unsigned char *buffer) |
79 |
{ |
80 |
unsigned char *sp = buffer; |
81 |
unsigned char *dp = buffer; |
82 |
int first = 1; |
83 |
while (*sp && (*sp <= 32 || 127 <= *sp)) |
84 |
sp++; |
85 |
while (*sp) { |
86 |
if (!first) |
87 |
*dp++ = ' '; |
88 |
first = 0; |
89 |
while (32 < *sp && *sp < 127) |
90 |
*dp++ = *sp++; |
91 |
while (*sp && (*sp <= 32 || 127 <= *sp)) |
92 |
sp++; |
93 |
} |
94 |
*dp = '\0'; |
95 |
} |
96 |
|
97 |
#define PASSWORD_PROMPT "PASSWORD_PROMPT " |
98 |
#define MAX_TRIALS "MAX_TRIALS " |
99 |
#define SLEEP_ON_FAILURE "SLEEP_ON_FAILURE " |
100 |
#define EXEC_ON_SUCCESS "EXEC_ON_SUCCESS " |
101 |
#define MAIL_COMMAND "MAIL_COMMAND " |
102 |
#define SINGLE_FAILURE_MESSAGE "SINGLE_FAILURE_MESSAGE " |
103 |
#define COMPLETE_FAILURE_MESSAGE "COMPLETE_FAILURE_MESSAGE " |
104 |
|
105 |
static char *password_prompt = "Enter\\040password"; |
106 |
static unsigned int max_trials = 3; |
107 |
static unsigned int sleep_on_failure = 3; |
108 |
static char *exec_on_success = "/bin/sh"; |
109 |
static char *mail_command = NULL; |
110 |
static char *single_failure_message = "Incorrect\\040password."; |
111 |
static char *complete_failure_message = "Authentication\\040failed."; |
112 |
|
113 |
static void show_help(const char *argv0) |
114 |
{ |
115 |
char *self = canonicalize_file_name(argv0); |
116 |
fprintf(stderr, |
117 |
"This is an interpreter for one-time-password-using-mail " |
118 |
"authentication script. To perform " |
119 |
"one-time-password-using-mail authentication, create a program " |
120 |
"like the following example.\n\n"); |
121 |
printf("#! %s\n" |
122 |
PASSWORD_PROMPT "%s\n" |
123 |
MAX_TRIALS "%d\n" |
124 |
SLEEP_ON_FAILURE "%d\n" |
125 |
EXEC_ON_SUCCESS "%s\n" |
126 |
MAIL_COMMAND "%s\n" |
127 |
SINGLE_FAILURE_MESSAGE "%s\n" |
128 |
COMPLETE_FAILURE_MESSAGE "%s\n", |
129 |
self, |
130 |
password_prompt, max_trials, sleep_on_failure, exec_on_success, |
131 |
"curl --data-binary @- https://your.server/path_to_cgi", |
132 |
single_failure_message, complete_failure_message); |
133 |
fprintf(stderr, |
134 |
"\n" |
135 |
PASSWORD_PROMPT "is a string to display before user's input.\n" |
136 |
MAX_TRIALS "is the maximal count the user can try.\n" |
137 |
SLEEP_ON_FAILURE "is the delay in second for each failure.\n" |
138 |
EXEC_ON_SUCCESS "is a program to execute when the " |
139 |
"authentication succeeded.\n" |
140 |
MAIL_COMMAND "is the command line to notify password.\n" |
141 |
SINGLE_FAILURE_MESSAGE "is a string to display when " |
142 |
"the authentication failed for once.\n" |
143 |
COMPLETE_FAILURE_MESSAGE "is a string to display when " |
144 |
"the authentication failed for all trials.\n" |
145 |
"The above example sends password to " |
146 |
"https://your.server/path_to_cgi using curl.\n" |
147 |
"Any character with value <= 0x20 or 0x7F <= values " |
148 |
"are regarded as a delimiter.\n" |
149 |
"You have to follow the character representation rule shown " |
150 |
"below.\n" |
151 |
" ASCII character (0x21 <= value <= 0x5B or 0x5D <= value " |
152 |
"<= 7E).\n" |
153 |
" \\\\ for \\ character (value = 0x5C).\n" |
154 |
" \\ooo style octal representation (value = any).\n" |
155 |
"The line "MAIL_COMMAND "is passed to system(), so " |
156 |
"escape appropriately as needed.\n"); |
157 |
} |
158 |
|
159 |
static int parse_script(const char *argv1) |
160 |
{ |
161 |
char buffer[8192]; |
162 |
FILE *fp = fopen(argv1, "r"); |
163 |
if (!fp) { |
164 |
fprintf(stderr, "Can't open %s\n", argv1); |
165 |
return 1; |
166 |
} |
167 |
while (memset(buffer, 0, sizeof(buffer)), |
168 |
fgets(buffer, sizeof(buffer) - 1, fp)) { |
169 |
char *cp = strchr(buffer, '\n'); |
170 |
unsigned int v; |
171 |
if (*cp) { |
172 |
*cp = '\0'; |
173 |
} else if (!feof(fp)) { |
174 |
fprintf(stderr, "Line too long.\n"); |
175 |
fclose(fp); |
176 |
return 1; |
177 |
} |
178 |
normalize_line(buffer); |
179 |
if (str_starts(buffer, PASSWORD_PROMPT)) |
180 |
password_prompt = str_dup(buffer); |
181 |
else if (sscanf(buffer, MAX_TRIALS "%u", &v) == 1) |
182 |
max_trials = v; |
183 |
else if (sscanf(buffer, SLEEP_ON_FAILURE "%u", &v) == 1) |
184 |
sleep_on_failure = v; |
185 |
else if (str_starts(buffer, EXEC_ON_SUCCESS)) |
186 |
exec_on_success = str_dup(buffer); |
187 |
else if (str_starts(buffer, MAIL_COMMAND)) |
188 |
mail_command = str_dup(buffer); |
189 |
else if (str_starts(buffer, SINGLE_FAILURE_MESSAGE)) |
190 |
single_failure_message = str_dup(buffer); |
191 |
else if (str_starts(buffer, COMPLETE_FAILURE_MESSAGE)) |
192 |
complete_failure_message = str_dup(buffer); |
193 |
} |
194 |
fclose(fp); |
195 |
if (!mail_command) { |
196 |
fprintf(stderr, "No mail command found.\n"); |
197 |
return 1; |
198 |
} |
199 |
password_prompt = str_dup(password_prompt); |
200 |
unescape_line(password_prompt); |
201 |
exec_on_success = str_dup(exec_on_success); |
202 |
unescape_line(exec_on_success); |
203 |
single_failure_message = str_dup(single_failure_message); |
204 |
unescape_line(single_failure_message); |
205 |
complete_failure_message = str_dup(complete_failure_message); |
206 |
unescape_line(complete_failure_message); |
207 |
return 0; |
208 |
} |
209 |
|
210 |
static int do_auth(void) |
211 |
{ |
212 |
char password[16]; |
213 |
char buffer[8192]; |
214 |
{ /* Create password. */ |
215 |
int i = 0; |
216 |
FILE *fp = fopen("/dev/urandom", "r"); |
217 |
if (!fp) { |
218 |
fprintf(stderr, "Can't open /dev/urandom .\n"); |
219 |
return 1; |
220 |
} |
221 |
memset(password, 0, sizeof(password)); |
222 |
while (i < sizeof(password)) { |
223 |
const unsigned int c = fgetc(fp); |
224 |
if (c < 10) |
225 |
password[i++] = c + '0'; |
226 |
} |
227 |
fclose(fp); |
228 |
} |
229 |
{ /* Send password. */ |
230 |
FILE *fp = popen(mail_command, "w"); |
231 |
if (!fp) { |
232 |
fprintf(stderr, "Can't send mail\n"); |
233 |
return 1; |
234 |
} |
235 |
fprintf(fp, "%s\n", password); |
236 |
pclose(fp); |
237 |
} |
238 |
/* fprintf(stderr, "%s\n", password); */ |
239 |
{ /* Check password. */ |
240 |
int trial; |
241 |
for (trial = 0; trial < max_trials; trial++) { |
242 |
char *cp; |
243 |
printf("%s\n", password_prompt); |
244 |
memset(buffer, 0, sizeof(buffer)); |
245 |
fgets(buffer, sizeof(buffer) - 1, stdin); |
246 |
cp = strchr(buffer, '\n'); |
247 |
if (cp) |
248 |
*cp = '\0'; |
249 |
if (!strcmp(buffer, password)) { |
250 |
execlp(exec_on_success, exec_on_success, NULL); |
251 |
fprintf(stderr, "Can't execute %s\n", |
252 |
exec_on_success); |
253 |
exit(1); |
254 |
} |
255 |
printf("%s\n", single_failure_message); |
256 |
sleep(sleep_on_failure); |
257 |
} |
258 |
} |
259 |
printf("%s\n", complete_failure_message); |
260 |
return 1; |
261 |
} |
262 |
|
263 |
int main(int argc, char *argv[]) |
264 |
{ |
265 |
unsetenv("SHELLOPTS"); /* Make sure popen() executes MAIL_COMMAND */ |
266 |
if (argc != 2 || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) { |
267 |
show_help(argv[0]); |
268 |
return 1; |
269 |
} |
270 |
if (parse_script(argv[1])) |
271 |
return 1; |
272 |
return do_auth(); |
273 |
} |