Added README
[pam_pcsc_cr.git] / authfile.c
1 /*
2 Copyright (c) 2013 Eugene Crosser
3
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
7
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely, subject to the following restrictions:
11
12     1. The origin of this software must not be misrepresented; you must
13     not claim that you wrote the original software. If you use this
14     software in a product, an acknowledgment in the product documentation
15     would be appreciated but is not required.
16
17     2. Altered source versions must be plainly marked as such, and must
18     not be misrepresented as being the original software.
19
20     3. This notice may not be removed or altered from any source
21     distribution.
22 */
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <pwd.h>
30 #include <time.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <alloca.h>
37 #include "authobj.h"
38 #include "authfile.h"
39
40 /*
41  * Template string may contain zero or one '~' and zero or one '?'.
42  * '~' at the beginning of the template string is substituted with
43  * the home directory of the userid. In any other position it is
44  * substituted with the userid itself. '?' is substituted with the
45  * tokenid. There is no way to make the resulting path contain '~'
46  * or '?'. If there is more than one '~' or '?', or if the '~' is
47  * at the beginning but userid does not resolve via getpwnam, or
48  * the character to substitute is present but the argument is NULL,
49  * NULL is returned. Otherwise, malloc()'ed area containg the path
50  * string.
51  */
52
53 static const char *template = "~/.pam_cr/auth";
54
55 void authfile_template(const char *str)
56 {
57         template = str;
58 }
59
60 static int path_size(const char *tokenid, const char *userid)
61 {
62         const char *usub;
63         const char *p, *q;
64         struct passwd *pw;
65
66         if ((p = strchr(template, '~')) != strrchr(template, '~')) return 0;
67         if ((q = strchr(template, '?')) != strrchr(template, '?')) return 0;
68         if (p && !userid) return 0;
69         if (q && !tokenid) return 0;
70         if (p == template) {
71                 pw = getpwnam(userid);
72                 if (!pw) return 0;
73                 usub = pw->pw_dir;
74         } else {
75                 usub = userid;
76         }
77         return strlen(template) + p?strlen(usub):0 + q?strlen(tokenid):0 + 1;
78 }
79
80 static void
81 make_path(char * const path, const char *tokenid, const char *userid)
82 {
83         const char *usub;
84         const char *p;
85         char *q;
86         struct passwd *pw;
87
88         path[0] = '\0';
89         if (template[0] == '~') {
90                 pw = getpwnam(userid);
91                 if (!pw) return;
92                 usub = pw->pw_dir;
93         } else {
94                 usub = userid;
95         }
96         q = path;
97         for (p = template; *p; p++) switch (*p) {
98         case '~':
99                 strcpy(q, usub);
100                 while (*q) q++;
101                 break;
102         case '?':
103                 strcpy(q, tokenid);
104                 while (*q) q++;
105                 break;
106         default:
107                 *q++ = *p;
108                 break;
109         }
110         *q = '\0';
111 }
112
113 int parse(char * const buf, const int argc, const char *argv[const])
114 {
115         char *p, *q;
116         int i;
117
118         for (i = 0, p = buf; *p; p = q+1, i++) {
119                 for (q = p; *q && *q != ':' && *q != '\r' && *q != '\n'; q++) ;
120                 *q = '\0';
121                 if (*p && i < argc) argv[i] = p;
122         }
123         return i != argc;
124 }
125
126 struct _auth_obj authfile(const char *tokenid,
127                 const char *userid, const char *password,
128                 void (*update_nonce)(char *nonce, const int nonsize),
129                 const unsigned char *secret, const int secsize,
130                 const unsigned char *payload, const int paylsize,
131                 struct _auth_chunk (*fetch_key)(const unsigned char *chal,
132                                                 const int csize))
133 {
134         struct _auth_obj ret = {0};
135         mode_t oldmask;
136         FILE *fp = NULL;
137         char *fn, *nfn;
138         int fnl;
139         char *buf = NULL;
140         struct {
141                 const char *tokenid;
142                 const char *userid;
143                 const char *nonce;
144                 const char *hablob;
145         } w = {"", NULL, NULL, NULL};
146         unsigned char *ablob = NULL;
147         int blobsize = 0;
148         char *newnonce;
149         int nonsize;
150         struct _auth_obj ao;
151
152         if ((fnl = path_size(tokenid, userid)) == 0) {
153                 ret.err = "authfile path impossible to build";
154                 return ret;
155         }
156         fn = alloca(fnl);
157         make_path(fn, tokenid, userid);
158         nfn = alloca(fnl+32);
159         snprintf(nfn, fnl+32, "%s.%d.%ld", fn, (int)getpid(), (long)time(NULL));
160         fp = fopen(fn, "r");
161         if (fp) {
162                 struct stat st;
163                 int fd = fileno(fp);
164
165                 if (fstat(fd, &st)) st.st_size = 2047;
166                 if (st.st_size > 2047) st.st_size = 2047;
167                 buf = alloca(st.st_size + 1);
168                 if (!fgets(buf, st.st_size + 1, fp)) {
169                         ret.err = strerror(errno);
170                 } else if (parse(buf, sizeof(w)/sizeof(char*),
171                                         (const char ** const)&w)){
172                         ret.err = "error: unparseable auth file";
173                 }
174                 fclose(fp);
175         }
176         if (ret.err) return ret;
177
178         if (w.hablob) {
179                 int hlen = strlen(w.hablob);
180                 if (hlen % 32 != 0) {
181                         ret.err = "error: auth string has wrong length";
182                 } else if (hlen !=
183                                 strspn(w.hablob, "0123456789abcdefABCDEF")) {
184                         ret.err = "error: auth string not hexadecimal";
185                 } else {
186                         int i;
187
188                         blobsize = hlen/2;
189                         ablob = alloca(blobsize);
190                         for (i = 0; i < blobsize; i++)
191                                 sscanf(&w.hablob[i*2], "%2hhx", &ablob[i]);
192                 }
193         }
194         if (ret.err) return ret;
195
196         nonsize = w.nonce ? strlen(w.nonce)*2 : 32;
197         if (nonsize < 32) nonsize = 32;
198         newnonce = alloca(nonsize);
199         if (w.nonce) strcpy(newnonce, w.nonce);
200         else memset(newnonce, 0, nonsize);
201         update_nonce(newnonce, nonsize);
202
203         ao = authobj(userid?userid:w.userid, password,
204                         w.nonce, newnonce, secret, secsize,
205                         payload, paylsize, ablob, blobsize,
206                         fetch_key);
207
208         if (ao.err) {
209                 ret.err = ao.err;
210                 if (ao.data) memset(ao.data, 0, ao.datasize);
211                 if (ao.payload) memset(ao.payload, 0, ao.paylsize);
212                 if (ao.buffer) free(ao.buffer);
213                 return ret;
214         }
215
216         oldmask = umask(077);
217         if ((fp = fopen(nfn, "w"))) {
218                 int i;
219
220                 if (fprintf(fp, "%s:%s:%s:", tokenid?tokenid:w.tokenid,
221                                 userid?userid:w.userid, newnonce) < 0) {
222                         ret.err = strerror(errno);
223                 } else for (i = 0; i < ao.datasize; i++)
224                     if (fprintf(fp, "%02x", ao.data[i]) < 0) {
225                         ret.err = strerror(errno);
226                 }
227                 fprintf(fp, "\n");
228                 if (fclose(fp) < 0) {
229                         ret.err = strerror(errno);
230                 }
231         } else {
232                 ret.err = strerror(errno);
233         }
234         (void)umask(oldmask);
235         if (ret.err) {
236                 unlink(nfn); /* may not exist but no matter */
237         } else if (rename(nfn, fn)) {
238                 ret.err = strerror(errno);
239         }
240
241         if (!ret.err) {
242                 int bufsize = (w.userid?strlen(w.userid)+1:0) + ao.paylsize + 1;
243                 if (bufsize) {
244                         if ((ret.buffer = malloc(bufsize)) == NULL) {
245                                 ret.err = "authfile malloc failed";
246                         } else {
247                                 unsigned char *p = ret.buffer;
248                                 if (w.userid) {
249                                         strcpy((char*)p, w.userid);
250                                         ret.data = p;
251                                         ret.datasize = strlen(w.userid)+1;
252                                         p += strlen(w.userid)+1;
253                                 }
254                                 if (ao.payload) {
255                                         memcpy(p, ao.payload, ao.paylsize);
256                                         p[ao.paylsize] = '\0';
257                                         ret.payload = p;
258                                         ret.paylsize = ao.paylsize+1;
259                                 }
260                         }
261                 }
262         }
263
264         if (ao.data) memset(ao.data, 0, ao.datasize);
265         if (ao.payload) memset(ao.payload, 0, ao.paylsize);
266         if (ao.buffer) free(ao.buffer);
267         return ret;
268 }