tidy build process
[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         struct stat st = {0};
140         char *buf = NULL;
141         struct {
142                 const char *tokenid;
143                 const char *userid;
144                 const char *nonce;
145                 const char *hablob;
146         } w = {"", NULL, NULL, NULL};
147         unsigned char *ablob = NULL;
148         int blobsize = 0;
149         char *newnonce;
150         int nonsize;
151         struct _auth_obj ao;
152
153         if ((fnl = path_size(tokenid, userid)) == 0) {
154                 ret.err = "authfile path impossible to build";
155                 return ret;
156         }
157         fn = alloca(fnl);
158         make_path(fn, tokenid, userid);
159         nfn = alloca(fnl+32);
160         snprintf(nfn, fnl+32, "%s.%d.%ld", fn, (int)getpid(), (long)time(NULL));
161         fp = fopen(fn, "r");
162         if (fp) {
163                 if (fstat(fileno(fp), &st)) st.st_size = 2047;
164                 if (st.st_size > 2047) st.st_size = 2047;
165                 buf = alloca(st.st_size + 1);
166                 if (!fgets(buf, st.st_size + 1, fp)) {
167                         ret.err = strerror(errno);
168                 } else if (parse(buf, sizeof(w)/sizeof(char*),
169                                         (const char ** const)&w)){
170                         ret.err = "error: unparseable auth file";
171                 }
172                 fclose(fp);
173         }
174         if (ret.err) return ret;
175
176         if (w.hablob) {
177                 int hlen = strlen(w.hablob);
178                 if (hlen % 32 != 0) {
179                         ret.err = "error: auth string has wrong length";
180                 } else if (hlen !=
181                                 strspn(w.hablob, "0123456789abcdefABCDEF")) {
182                         ret.err = "error: auth string not hexadecimal";
183                 } else {
184                         int i;
185
186                         blobsize = hlen/2;
187                         ablob = alloca(blobsize);
188                         for (i = 0; i < blobsize; i++)
189                                 sscanf(&w.hablob[i*2], "%2hhx", &ablob[i]);
190                 }
191         }
192         if (ret.err) return ret;
193
194         nonsize = w.nonce ? strlen(w.nonce)*2 : 32;
195         if (nonsize < 32) nonsize = 32;
196         newnonce = alloca(nonsize);
197         if (w.nonce) strcpy(newnonce, w.nonce);
198         else memset(newnonce, 0, nonsize);
199         update_nonce(newnonce, nonsize);
200
201         ao = authobj(userid?userid:w.userid, password,
202                         w.nonce, newnonce, secret, secsize,
203                         payload, paylsize, ablob, blobsize,
204                         fetch_key);
205
206         if (ao.err) {
207                 ret.err = ao.err;
208                 if (ao.data) memset(ao.data, 0, ao.datasize);
209                 if (ao.payload) memset(ao.payload, 0, ao.paylsize);
210                 if (ao.buffer) free(ao.buffer);
211                 return ret;
212         }
213
214         oldmask = umask(077);
215         if ((fp = fopen(nfn, "w"))) {
216                 int i;
217
218                 if (fprintf(fp, "%s:%s:%s:", tokenid?tokenid:w.tokenid,
219                                 userid?userid:w.userid, newnonce) < 0) {
220                         ret.err = strerror(errno);
221                 } else for (i = 0; i < ao.datasize; i++)
222                     if (fprintf(fp, "%02x", ao.data[i]) < 0) {
223                         ret.err = strerror(errno);
224                 }
225                 fprintf(fp, "\n");
226                 if (st.st_uid || st.st_gid) {
227                         if (fchown(fileno(fp), st.st_uid, st.st_gid)) /*ign*/;
228                 }
229                 if (fclose(fp) < 0) {
230                         ret.err = strerror(errno);
231                 }
232         } else {
233                 ret.err = strerror(errno);
234         }
235         (void)umask(oldmask);
236         if (ret.err) {
237                 unlink(nfn); /* may not exist but no matter */
238         } else if (rename(nfn, fn)) {
239                 ret.err = strerror(errno);
240         }
241
242         if (!ret.err) {
243                 int bufsize = (w.userid?strlen(w.userid)+1:0) + ao.paylsize + 1;
244                 if (bufsize) {
245                         if ((ret.buffer = malloc(bufsize)) == NULL) {
246                                 ret.err = "authfile malloc failed";
247                         } else {
248                                 unsigned char *p = ret.buffer;
249                                 if (w.userid) {
250                                         strcpy((char*)p, w.userid);
251                                         ret.data = p;
252                                         ret.datasize = strlen(w.userid)+1;
253                                         p += strlen(w.userid)+1;
254                                 }
255                                 if (ao.payload) {
256                                         memcpy(p, ao.payload, ao.paylsize);
257                                         p[ao.paylsize] = '\0';
258                                         ret.payload = p;
259                                         ret.paylsize = ao.paylsize+1;
260                                 }
261                         }
262                 }
263         }
264
265         if (ao.data) memset(ao.data, 0, ao.datasize);
266         if (ao.payload) memset(ao.payload, 0, ao.paylsize);
267         if (ao.buffer) free(ao.buffer);
268         return ret;
269 }