Add full PAM support for account management and sessions to su(1).
[dragonfly.git] / lib / libskey / skeylogin.c
1 /*   Login code for S/KEY Authentication.  S/KEY is a trademark
2  *   of Bellcore.
3  *
4  *   Mink is the former name of the S/KEY authentication system.
5  *   Many references for mink  may still be found in this program.
6  *
7  * $FreeBSD: src/lib/libskey/skeylogin.c,v 1.14.6.1 2000/07/18 11:38:24 sheldonh Exp $
8  * $DragonFly: src/lib/libskey/skeylogin.c,v 1.5 2008/09/30 16:57:06 swildner Exp $
9  */
10
11 #include <sys/param.h>
12 #include <sys/stat.h>
13 #include <sys/time.h>
14 #include <sys/resource.h>
15
16 #include <errno.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <time.h>
21 #include <utmp.h>
22
23 #include "skey.h"
24 #include "pathnames.h"
25
26 static char *skipspace (char *);
27
28 #define setpriority(x,y,z)      /* nothing */
29
30 static const char *month[12] = {
31         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
32         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
33 };
34
35 /* Look up skey info for user 'name'. If successful, fill in the caller's
36  * skey structure and return 0. If unsuccessful (e.g., if name is unknown)
37  * return -1. If an optional challenge string buffer is given, update it.
38  *
39  * The file read/write pointer is left at the start of the
40  * record.
41  */
42 int
43 skeyinfo(struct skey *mp, const char *name, char *ss)
44 {
45         int rval;
46
47         rval = skeylookup(mp,name);
48         switch(rval){
49         case -1:        /* File error */
50                 return -1;
51         case 0:         /* Lookup succeeded */
52                 if (ss != 0) {
53                         sprintf(ss, "s/key %d %s",mp->n - 1,mp->seed);
54                         fclose(mp->keyfile);
55                 }
56                 return 0;
57         case 1:         /* User not found */
58                 fclose(mp->keyfile);
59                 return -1;
60         }
61         return -1;      /* Can't happen */
62 }
63
64 /* Return  a skey challenge string for user 'name'. If successful,
65  * fill in the caller's skey structure and return 0. If unsuccessful
66  * (e.g., if name is unknown) return -1.
67  *
68  * The file read/write pointer is left at the start of the
69  * record.
70  */
71 int
72 skeychallenge(struct skey *mp, const char *name, char *ss)
73 {
74         int rval;
75
76         rval = skeylookup(mp,name);
77         switch(rval){
78         case -1:        /* File error */
79                 return -1;
80         case 0:         /* Lookup succeeded, issue challenge */
81                 sprintf(ss, "s/key %d %s",mp->n - 1,mp->seed);
82                 return 0;
83         case 1:         /* User not found */
84                 fclose(mp->keyfile);
85                 return -1;
86         }
87         return -1;      /* Can't happen */
88 }
89
90 /* Find an entry in the One-time Password database.
91  * Return codes:
92  * -1: error in opening database
93  *  0: entry found, file R/W pointer positioned at beginning of record
94  *  1: entry not found, file R/W pointer positioned at EOF
95  */
96 int
97 skeylookup(struct skey *mp, const char *name)
98 {
99         int found;
100         size_t len;
101         long recstart = 0;
102         char *cp, *p;
103         struct stat statbuf;
104         mode_t oldmask;
105
106         /* See if the _PATH_SKEYFILE exists, and create it if not */
107         if(stat(_PATH_SKEYFILE,&statbuf) == -1 && errno == ENOENT){
108                 oldmask = umask(S_IRWXG|S_IRWXO);
109                 mp->keyfile = fopen(_PATH_SKEYFILE,"w+");
110                 (void)umask(oldmask);
111         } else {
112                 /* Otherwise open normally for update */
113                 mp->keyfile = fopen(_PATH_SKEYFILE,"r+");
114         }
115         if(mp->keyfile == NULL)
116                 return -1;
117
118         /* Look up user name in database */
119         len = strlen(name);
120         if(len > UT_NAMESIZE)
121                 len = UT_NAMESIZE;
122         found = 0;
123         while(!feof(mp->keyfile)){
124                 recstart = ftell(mp->keyfile);
125                 mp->recstart = recstart;
126                 if(fgets(mp->buf,sizeof(mp->buf),mp->keyfile) != mp->buf){
127                         break;
128                 }
129                 rip(mp->buf);
130                 if(mp->buf[0] == '#')
131                         continue;       /* Comment */
132                 p = mp->buf;
133                 while ((cp = strsep(&p, " \t")) != NULL && *cp == '\0')
134                         ;
135                 if((mp->logname = cp) == NULL)
136                         continue;
137                 while ((cp = strsep(&p, " \t")) != NULL && *cp == '\0')
138                         ;
139                 if(cp == NULL)
140                         continue;
141                 mp->n = atoi(cp);
142                 while ((cp = strsep(&p, " \t")) != NULL && *cp == '\0')
143                         ;
144                 if((mp->seed = cp) == NULL)
145                         continue;
146                 while ((cp = strsep(&p, " \t")) != NULL && *cp == '\0')
147                         ;
148                 if((mp->val = cp) == NULL)
149                         continue;
150                 if(strlen(mp->logname) == len
151                  && strncmp(mp->logname,name,len) == 0){
152                         found = 1;
153                         break;
154                 }
155         }
156         if(found){
157                 fseek(mp->keyfile,recstart,0);
158                 return 0;
159         } else
160                 return 1;
161 }
162 /* Verify response to a s/key challenge.
163  *
164  * Return codes:
165  * -1: Error of some sort; database unchanged
166  *  0:  Verify successful, database updated
167  *  1:  Verify failed, database unchanged
168  *
169  * The database file is always closed by this call.
170  */
171 int
172 skeyverify(struct skey *mp, char *response)
173 {
174         char key[8];
175         char fkey[8];
176         char filekey[8];
177         time_t now;
178         struct tm *tm;
179         char tbuf[27], fbuf[20];
180         char *cp, *p;
181
182         time(&now);
183         tm = localtime(&now);
184 /* can't use %b here, because it can be in national form */
185         strftime(fbuf, sizeof(fbuf), "%d,%Y %T", tm);
186         snprintf(tbuf, sizeof(tbuf), " %s %s", month[tm->tm_mon], fbuf);
187
188         if(response == NULL){
189                 fclose(mp->keyfile);
190                 return -1;
191         }
192         rip(response);
193
194         /* Convert response to binary */
195         if(etob(key,response) != 1 && atob8(key,response) != 0){
196                 /* Neither english words or ascii hex */
197                 fclose(mp->keyfile);
198                 return -1;
199         }
200
201         /* Compute fkey = f(key) */
202         memcpy(fkey,key,sizeof(key));
203         f(fkey);
204         /* in order to make the window of update as short as possible
205            we must do the comparison here and if OK write it back
206            other wise the same password can be used twice to get in
207            to the system
208         */
209
210         setpriority(PRIO_PROCESS, 0, -4);
211
212         /* reread the file record NOW*/
213
214         fseek(mp->keyfile,mp->recstart,0);
215         if(fgets(mp->buf,sizeof(mp->buf),mp->keyfile) != mp->buf){
216                 setpriority(PRIO_PROCESS, 0, 0);
217                 fclose(mp->keyfile);
218                 return -1;
219         }
220         rip(mp->buf);
221         p = mp->buf;
222         while ((cp = strsep(&p, " \t")) != NULL && *cp == '\0')
223                 ;
224         mp->logname = cp;
225         while ((cp = strsep(&p, " \t")) != NULL && *cp == '\0')
226                 ;
227         while ((cp = strsep(&p, " \t")) != NULL && *cp == '\0')
228                 ;
229         mp->seed = cp;
230         while ((cp = strsep(&p, " \t")) != NULL && *cp == '\0')
231                 ;
232         mp->val = cp;
233         /* And convert file value to hex for comparison */
234         atob8(filekey,mp->val);
235
236         /* Do actual comparison */
237         if(memcmp(filekey,fkey,8) != 0){
238                 /* Wrong response */
239                 setpriority(PRIO_PROCESS, 0, 0);
240                 fclose(mp->keyfile);
241                 return 1;
242         }
243
244         /* Update key in database by overwriting entire record. Note
245          * that we must write exactly the same number of bytes as in
246          * the original record (note fixed width field for N)
247          */
248         btoa8(mp->val,key);
249         mp->n--;
250         fseek(mp->keyfile,mp->recstart,0);
251         fprintf(mp->keyfile,"%s %04d %-16s %s %-21s\n",mp->logname,mp->n,mp->seed,
252          mp->val, tbuf);
253
254         fclose(mp->keyfile);
255
256         setpriority(PRIO_PROCESS, 0, 0);
257         return 0;
258 }
259
260
261 /* Convert 8-byte hex-ascii string to binary array
262  * Returns 0 on success, -1 on error
263  */
264 int
265 atob8(char *out, char *in)
266 {
267         int i;
268         int val;
269
270         if(in == NULL || out == NULL)
271                 return -1;
272
273         for(i=0;i<8;i++){
274                 if((in = skipspace(in)) == NULL)
275                         return -1;
276                 if((val = htoi(*in++)) == -1)
277                         return -1;
278                 *out = val << 4;
279
280                 if((in = skipspace(in)) == NULL)
281                         return -1;
282                 if((val = htoi(*in++)) == -1)
283                         return -1;
284                 *out++ |= val;
285         }
286         return 0;
287 }
288
289 static
290 char *
291 skipspace(char *cp)
292 {
293         while(*cp == ' ' || *cp == '\t')
294                 cp++;
295
296         if(*cp == '\0')
297                 return NULL;
298         else
299                 return cp;
300 }
301
302 /* Convert 8-byte binary array to hex-ascii string */
303 int
304 btoa8(char *out, char *in)
305 {
306         int i;
307
308         if(in == NULL || out == NULL)
309                 return -1;
310
311         for(i=0;i<8;i++){
312                 sprintf(out,"%02x",*in++ & 0xff);
313                 out += 2;
314         }
315         return 0;
316 }
317
318
319 /* Convert hex digit to binary integer */
320 int
321 htoi(char c)
322 {
323         if('0' <= c && c <= '9')
324                 return c - '0';
325         if('a' <= c && c <= 'f')
326                 return 10 + c - 'a';
327         if('A' <= c && c <= 'F')
328                 return 10 + c - 'A';
329         return -1;
330 }