auth.c 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. /*
  2. auth.c -- Authorization Management
  3. This modules supports a user/role/ability based authorization scheme.
  4. In this scheme Users have passwords and can have multiple roles. A role is associated with the ability to do
  5. things like "admin" or "user" or "support". A role may have abilities (which are typically verbs) like "add",
  6. "shutdown".
  7. When the web server starts up, it loads a route and authentication configuration file that specifies the Users,
  8. Roles and Routes. Routes specify the required abilities to access URLs by specifying the URL prefix. Once logged
  9. in, the user's abilities are tested against the route abilities. When the web server receivess a request, the set of
  10. Routes is consulted to select the best route. If the routes requires abilities, the user must be logged in and
  11. authenticated.
  12. Three authentication backend protocols are supported:
  13. HTTP basic authentication which uses browser dialogs and clear text passwords (insecure unless over TLS)
  14. HTTP digest authentication which uses browser dialogs
  15. Web form authentication which uses a web page form to login (insecure unless over TLS)
  16. Copyright (c) All Rights Reserved. See details at the end of the file.
  17. */
  18. /********************************* Includes ***********************************/
  19. #include "goahead.h"
  20. #include "user.h"
  21. #include "cJSON.h"
  22. #if ME_GOAHEAD_AUTH
  23. #if ME_COMPILER_HAS_PAM
  24. #include <security/pam_appl.h>
  25. #endif
  26. /*********************************** Locals ***********************************/
  27. static WebsHash users = -1;
  28. static WebsHash roles = -1;
  29. static char *masterSecret;
  30. static int autoLogin = ME_GOAHEAD_AUTO_LOGIN;
  31. //static WebsVerify verifyPassword = websVerifyPasswordFromFile;
  32. static WebsVerify verifyPassword = websVerifyPasswordFromBMC;
  33. #if ME_COMPILER_HAS_PAM
  34. typedef struct {
  35. char *name;
  36. char *password;
  37. } UserInfo;
  38. #if MACOSX
  39. typedef int Gid;
  40. #else
  41. typedef gid_t Gid;
  42. #endif
  43. #endif
  44. /********************************** Forwards **********************************/
  45. static void computeAbilities(WebsHash abilities, char *role, int depth);
  46. static void computeUserAbilities(WebsUser *user);
  47. static WebsUser *createUser(char *username, char *password, char *roles);
  48. static void freeRole(WebsRole *rp);
  49. static void freeUser(WebsUser *up);
  50. static void logoutServiceProc(Webs *wp);
  51. static void loginServiceProc(Webs *wp);
  52. #if ME_GOAHEAD_JAVASCRIPT && FUTURE
  53. static int jsCan(int jsid, Webs *wp, int argc, char **argv);
  54. #endif
  55. #if ME_GOAHEAD_DIGEST
  56. static char *calcDigest(Webs *wp, char *username, char *password);
  57. static char *createDigestNonce(Webs *wp);
  58. static char *parseDigestNonce(char *nonce, char **secret, char **realm, WebsTime *when);
  59. #endif
  60. #if ME_COMPILER_HAS_PAM
  61. static int pamChat(int msgCount, const struct pam_message **msg, struct pam_response **resp, void *data);
  62. #endif
  63. /************************************ Code ************************************/
  64. PUBLIC bool websAuthenticate(Webs *wp)
  65. {
  66. WebsRoute *route;
  67. char *username;
  68. int cached;
  69. assert(wp);
  70. assert(wp->route);
  71. route = wp->route;
  72. if (!route || !route->authType || autoLogin) {
  73. /* Authentication not required */
  74. return 1;
  75. }
  76. cached = 0;
  77. if (wp->cookie && websGetSession(wp, 0) != 0) {
  78. /*
  79. Retrieve authentication state from the session storage. Faster than re-authenticating.
  80. */
  81. if ((username = (char*) websGetSessionVar(wp, WEBS_SESSION_USERNAME, 0)) != 0) {
  82. cached = 1;
  83. wfree(wp->username);
  84. wp->username = sclone(username);
  85. }
  86. }
  87. if (!cached) {
  88. if (wp->authType && !smatch(wp->authType, route->authType)) {
  89. websError(wp, HTTP_CODE_UNAUTHORIZED, "Access denied. Wrong authentication protocol type.");
  90. return 0;
  91. }
  92. if (wp->authDetails && route->parseAuth) {
  93. if (!(route->parseAuth)(wp)) {
  94. wp->username = 0;
  95. }
  96. }
  97. if (!wp->username || !*wp->username) {
  98. if (route->askLogin) {
  99. (route->askLogin)(wp);
  100. }
  101. websRedirectByStatus(wp, HTTP_CODE_UNAUTHORIZED);
  102. return 0;
  103. }
  104. if (!(route->verify)(wp)) {
  105. if (route->askLogin) {
  106. (route->askLogin)(wp);
  107. }
  108. websRedirectByStatus(wp, HTTP_CODE_UNAUTHORIZED);
  109. return 0;
  110. }
  111. /*
  112. Store authentication state and user in session storage
  113. */
  114. if (websGetSession(wp, 1) != 0) {
  115. websSetSessionVar(wp, WEBS_SESSION_USERNAME, wp->username);
  116. }
  117. }
  118. return 1;
  119. }
  120. PUBLIC int websOpenAuth(int minimal)
  121. {
  122. char sbuf[64];
  123. assert(minimal == 0 || minimal == 1);
  124. if ((users = hashCreate(-1)) < 0) {
  125. return -1;
  126. }
  127. if ((roles = hashCreate(-1)) < 0) {
  128. return -1;
  129. }
  130. if (!minimal) {
  131. fmt(sbuf, sizeof(sbuf), "%x:%x", rand(), time(0));
  132. masterSecret = websMD5(sbuf);
  133. #if ME_GOAHEAD_JAVASCRIPT && FUTURE
  134. websJsDefine("can", jsCan);
  135. #endif
  136. websDefineAction("login", loginServiceProc);
  137. websDefineAction("logout", logoutServiceProc);
  138. }
  139. if (smatch(ME_GOAHEAD_AUTH_STORE, "file")) {
  140. verifyPassword = websVerifyPasswordFromFile;
  141. #if ME_COMPILER_HAS_PAM
  142. } else if (smatch(ME_GOAHEAD_AUTH_STORE, "pam")) {
  143. verifyPassword = websVerifyPasswordFromPam;
  144. #endif
  145. } else if (smatch(ME_GOAHEAD_AUTH_STORE, "BMC")) {
  146. verifyPassword = websVerifyPasswordFromBMC;
  147. }
  148. return 0;
  149. }
  150. PUBLIC void websCloseAuth()
  151. {
  152. WebsKey *key, *next;
  153. wfree(masterSecret);
  154. if (users >= 0) {
  155. for (key = hashFirst(users); key; key = next) {
  156. next = hashNext(users, key);
  157. freeUser(key->content.value.symbol);
  158. }
  159. hashFree(users);
  160. users = -1;
  161. }
  162. if (roles >= 0) {
  163. for (key = hashFirst(roles); key; key = next) {
  164. next = hashNext(roles, key);
  165. freeRole(key->content.value.symbol);
  166. }
  167. hashFree(roles);
  168. roles = -1;
  169. }
  170. }
  171. #if KEEP
  172. PUBLIC int websWriteAuthFile(char *path)
  173. {
  174. FILE *fp;
  175. WebsKey *kp, *ap;
  176. WebsRole *role;
  177. WebsUser *user;
  178. char *tempFile;
  179. assert(path && *path);
  180. tempFile = websTempFile(NULL, NULL);
  181. if ((fp = fopen(tempFile, "w" FILE_TEXT)) == 0) {
  182. error("Cannot open %s", tempFile);
  183. wfree(tempFile);
  184. return -1;
  185. }
  186. fprintf(fp, "#\n# %s - Authorization data\n#\n\n", basename(path));
  187. if (roles >= 0) {
  188. for (kp = hashFirst(roles); kp; kp = hashNext(roles, kp)) {
  189. role = kp->content.value.symbol;
  190. fprintf(fp, "role name=%s abilities=", kp->name.value.string);
  191. for (ap = hashFirst(role->abilities); ap; ap = hashNext(role->abilities, ap)) {
  192. fprintf(fp, "%s,", ap->name.value.string);
  193. }
  194. fputc('\n', fp);
  195. }
  196. fputc('\n', fp);
  197. }
  198. if (users >= 0) {
  199. for (kp = hashFirst(users); kp; kp = hashNext(users, kp)) {
  200. user = kp->content.value.symbol;
  201. fprintf(fp, "user name=%s password=%s roles=%s", user->name, user->password, user->roles);
  202. fputc('\n', fp);
  203. }
  204. }
  205. fclose(fp);
  206. unlink(path);
  207. if (rename(tempFile, path) < 0) {
  208. error("Cannot create new %s", path);
  209. wfree(tempFile);
  210. return -1;
  211. }
  212. wfree(tempFile);
  213. return 0;
  214. }
  215. #endif
  216. static WebsUser *createUser(char *username, char *password, char *roles) //jimbo todo
  217. {
  218. WebsUser *user;
  219. assert(username);
  220. if ((user = walloc(sizeof(WebsUser))) == 0) {
  221. return 0;
  222. }
  223. user->name = sclone(username);
  224. user->roles = sclone(roles);
  225. user->password = sclone(password);
  226. user->abilities = -1;
  227. return user;
  228. }
  229. WebsUser *websAddUser(char *username, char *password, char *roles) //jimbo todo
  230. {
  231. WebsUser *user;
  232. if (!username) {
  233. error("User is missing name");
  234. return 0;
  235. }
  236. if (websLookupUser(username)) {
  237. error("User %s already exists", username);
  238. /* Already exists */
  239. return 0;
  240. }
  241. if ((user = createUser(username, password, roles)) == 0) {
  242. return 0;
  243. }
  244. if (hashEnter(users, username, valueSymbol(user), 0) == 0) {
  245. return 0;
  246. }
  247. return user;
  248. }
  249. PUBLIC int websRemoveUser(char *username) //jimbo todo
  250. {
  251. WebsKey *key;
  252. assert(username);
  253. if ((key = hashLookup(users, username)) != 0) {
  254. freeUser(key->content.value.symbol);
  255. }
  256. return hashDelete(users, username);
  257. }
  258. static void freeUser(WebsUser *up) //jimbo delete
  259. {
  260. assert(up);
  261. hashFree(up->abilities);
  262. wfree(up->name);
  263. wfree(up->password);
  264. wfree(up->roles);
  265. wfree(up);
  266. }
  267. PUBLIC int websSetUserPassword(char *username, char *password) //jimbo todo
  268. {
  269. WebsUser *user;
  270. assert(username);
  271. if ((user = websLookupUser(username)) == 0) {
  272. return -1;
  273. }
  274. wfree(user->password);
  275. user->password = sclone(password);
  276. return 0;
  277. }
  278. PUBLIC int websSetUserRoles(char *username, char *roles) //jimbo todo
  279. {
  280. WebsUser *user;
  281. assert(username);
  282. if ((user = websLookupUser(username)) == 0) {
  283. return -1;
  284. }
  285. wfree(user->roles);
  286. user->roles = sclone(roles);
  287. computeUserAbilities(user);
  288. return 0;
  289. }
  290. WebsUser *websLookupUser(char *username)
  291. {
  292. WebsKey *key;
  293. assert(username);
  294. if ((key = hashLookup(users, username)) == 0) {
  295. return 0;
  296. }
  297. return (WebsUser*) key->content.value.symbol;
  298. }
  299. static void computeAbilities(WebsHash abilities, char *role, int depth)
  300. {
  301. WebsRole *rp;
  302. WebsKey *key;
  303. assert(abilities >= 0);
  304. assert(role && *role);
  305. assert(depth >= 0);
  306. if (depth > 20) {
  307. error("Recursive ability definition for %s", role);
  308. return;
  309. }
  310. if (roles >= 0) {
  311. if ((key = hashLookup(roles, role)) != 0) {
  312. rp = (WebsRole*) key->content.value.symbol;
  313. for (key = hashFirst(rp->abilities); key; key = hashNext(rp->abilities, key)) {
  314. computeAbilities(abilities, key->name.value.string, ++depth);
  315. }
  316. } else {
  317. hashEnter(abilities, role, valueInteger(0), 0);
  318. }
  319. }
  320. }
  321. static void computeUserAbilities(WebsUser *user)
  322. {
  323. char *ability, *roles, *tok;
  324. assert(user);
  325. if ((user->abilities = hashCreate(-1)) == 0) {
  326. return;
  327. }
  328. roles = sclone(user->roles);
  329. for (ability = stok(roles, " \t,", &tok); ability; ability = stok(NULL, " \t,", &tok)) {
  330. computeAbilities(user->abilities, ability, 0);
  331. }
  332. #if ME_DEBUG
  333. {
  334. WebsKey *key;
  335. trace(5 | WEBS_RAW_MSG, "User \"%s\" has abilities: ", user->name);
  336. for (key = hashFirst(user->abilities); key; key = hashNext(user->abilities, key)) {
  337. trace(5 | WEBS_RAW_MSG, "%s ", key->name.value.string);
  338. ability = key->name.value.string;
  339. }
  340. trace(5, "");
  341. }
  342. #endif
  343. wfree(roles);
  344. }
  345. PUBLIC void websComputeAllUserAbilities()
  346. {
  347. WebsUser *user;
  348. WebsKey *sym;
  349. if (users) {
  350. for (sym = hashFirst(users); sym; sym = hashNext(users, sym)) {
  351. user = (WebsUser*) sym->content.value.symbol;
  352. computeUserAbilities(user);
  353. }
  354. }
  355. }
  356. WebsRole *websAddRole(char *name, WebsHash abilities)
  357. {
  358. WebsRole *rp;
  359. if (!name) {
  360. error("Role is missing name");
  361. return 0;
  362. }
  363. if (hashLookup(roles, name)) {
  364. error("Role %s already exists", name);
  365. /* Already exists */
  366. return 0;
  367. }
  368. if ((rp = walloc(sizeof(WebsRole))) == 0) {
  369. return 0;
  370. }
  371. rp->abilities = abilities;
  372. if (hashEnter(roles, name, valueSymbol(rp), 0) == 0) {
  373. return 0;
  374. }
  375. return rp;
  376. }
  377. static void freeRole(WebsRole *rp)
  378. {
  379. assert(rp);
  380. hashFree(rp->abilities);
  381. wfree(rp);
  382. }
  383. /*
  384. Does not recompute abilities for users that use this role
  385. */
  386. PUBLIC int websRemoveRole(char *name)
  387. {
  388. WebsRole *rp;
  389. WebsKey *sym;
  390. assert(name && *name);
  391. if (roles) {
  392. if ((sym = hashLookup(roles, name)) == 0) {
  393. return -1;
  394. }
  395. rp = sym->content.value.symbol;
  396. hashFree(rp->abilities);
  397. wfree(rp);
  398. return hashDelete(roles, name);
  399. }
  400. return -1;
  401. }
  402. PUBLIC WebsHash websGetUsers()
  403. {
  404. return users;
  405. }
  406. PUBLIC WebsHash websGetRoles()
  407. {
  408. return roles;
  409. }
  410. PUBLIC bool websLoginUser(Webs *wp, char *username, char *password)
  411. {
  412. assert(wp);
  413. assert(wp->route);
  414. assert(username);
  415. assert(password);
  416. printf("websLoginUser, username: %s, password: %s\n", username, password);
  417. if (!wp->route || !wp->route->verify) {
  418. return 0;
  419. }
  420. wfree(wp->username);
  421. wp->username = sclone(username);
  422. wfree(wp->password);
  423. wp->password = sclone(password);
  424. if (!(wp->route->verify)(wp)) {
  425. trace(2, "Password does not match");
  426. return 0;
  427. }
  428. trace(2, "Login successful for %s", username);
  429. websCreateSession(wp);
  430. websSetSessionVar(wp, WEBS_SESSION_USERNAME, wp->username);
  431. return 1;
  432. }
  433. PUBLIC bool websLogoutUser(Webs *wp)
  434. {
  435. assert(wp);
  436. websRemoveSessionVar(wp, WEBS_SESSION_USERNAME);
  437. websDestroySession(wp);
  438. if (smatch(wp->authType, "basic") || smatch(wp->authType, "digest")) {
  439. websError(wp, HTTP_CODE_UNAUTHORIZED, "Logged out.");
  440. return 0;
  441. }
  442. websRedirectByStatus(wp, HTTP_CODE_OK);
  443. return 1;
  444. }
  445. /*
  446. Internal login service routine for Form-based auth
  447. */
  448. static void loginServiceProc(Webs *wp)
  449. {
  450. WebsRoute *route;
  451. assert(wp);
  452. route = wp->route;
  453. assert(route);
  454. char *pStr;
  455. cJSON * root = cJSON_CreateObject();
  456. cJSON * data = cJSON_CreateObject();
  457. cJSON_AddItemToObject(root, "data", data);//根节点下添加
  458. cJSON_AddStringToObject(root, "msg", "");
  459. if (websLoginUser(wp, websGetVar(wp, "username", ""), websGetVar(wp, "password", ""))) {
  460. /* If the application defines a referrer session var, redirect to that */
  461. char *referrer;
  462. // if ((referrer = websGetSessionVar(wp, "referrer", 0)) != 0) {
  463. // websRedirect(wp, referrer);
  464. // } else {
  465. // websRedirectByStatus(wp, HTTP_CODE_OK);
  466. // }
  467. cJSON_AddNumberToObject(root, "code", 200);
  468. websSetSessionVar(wp, "loginStatus", "ok");
  469. } else {
  470. // if (route->askLogin) {
  471. // (route->askLogin)(wp);
  472. // }
  473. websSetSessionVar(wp, "loginStatus", "failed");
  474. // websRedirectByStatus(wp, HTTP_CODE_UNAUTHORIZED);
  475. cJSON_AddNumberToObject(root, "code", 201);
  476. }
  477. char *loginStatus = (char*) websGetSessionVar(wp, "loginStatus", 0);
  478. //printf(">>>>>>>>>>>>>>>>>%s<<<<<<<<<<<11<<<<<<<<<<loginStatus\n", loginStatus);
  479. pStr = cJSON_PrintUnformatted(root);
  480. printf("---> cJSON Str: %d \n%s\n", strlen(pStr), pStr);
  481. websSetStatus(wp, 200);
  482. websWriteHeaders(wp, -1, 0); //Set length to -1 if unknown and transfer-chunk-encoding will be employed.
  483. websWriteEndHeaders(wp);
  484. int ret;
  485. ret = websWrite(wp, "%s", pStr);
  486. if(ret < 0)
  487. printf("websWrite error\n");
  488. ret = websFlush(wp, 0); //wait for all data to be written to the socket
  489. // if(ret != 1)
  490. // printf("websFlush error, ret = %d\n", ret);
  491. websDone(wp);
  492. if(pStr)
  493. wfree(pStr);
  494. if(root)
  495. cJSON_Delete(root);
  496. }
  497. static void logoutServiceProc(Webs *wp)
  498. {
  499. websLogoutUser(wp);
  500. }
  501. static void basicLogin(Webs *wp)
  502. {
  503. assert(wp);
  504. assert(wp->route);
  505. wfree(wp->authResponse);
  506. wp->authResponse = sfmt("Basic realm=\"%s\"", ME_GOAHEAD_REALM);
  507. }
  508. void websSetPasswordStoreVerify(WebsVerify verify)
  509. {
  510. verifyPassword = verify;
  511. }
  512. WebsVerify websGetPasswordStoreVerify()
  513. {
  514. return verifyPassword;
  515. }
  516. PUBLIC bool websVerifyPasswordFromFile(Webs *wp)
  517. {
  518. char passbuf[ME_GOAHEAD_LIMIT_PASSWORD * 3 + 3];
  519. bool success;
  520. assert(wp);
  521. if (!wp->user && (wp->user = websLookupUser(wp->username)) == 0) {
  522. trace(5, "verifyUser: Unknown user \"%s\"", wp->username);
  523. return 0;
  524. }
  525. /*
  526. Verify the password. If using Digest auth, we compare the digest of the password.
  527. Otherwise we encode the plain-text password and compare that
  528. */
  529. if (!wp->encoded) {
  530. fmt(passbuf, sizeof(passbuf), "%s:%s:%s", wp->username, ME_GOAHEAD_REALM, wp->password);
  531. wfree(wp->password);
  532. wp->password = websMD5(passbuf);
  533. printf("---> encoded password: %s\n", wp->password);
  534. wp->encoded = 1;
  535. }
  536. if (wp->digest) {
  537. success = smatch(wp->password, wp->digest);
  538. printf("---> password: %s, digest: %s\n", wp->password, wp->digest);
  539. } else {
  540. success = smatch(wp->password, wp->user->password);
  541. printf("---> password: %s, user->password: %s\n", wp->password, wp->user->password);
  542. }
  543. if (success) {
  544. trace(5, "User \"%s\" authenticated", wp->username);
  545. } else {
  546. trace(5, "Password for user \"%s\" failed to authenticate", wp->username);
  547. }
  548. return success;
  549. }
  550. #if ME_COMPILER_HAS_PAM
  551. /*
  552. Copy this routine if creating your own custom password store back end
  553. */
  554. PUBLIC bool websVerifyPasswordFromPam(Webs *wp)
  555. {
  556. WebsBuf abilities;
  557. pam_handle_t *pamh;
  558. UserInfo info;
  559. struct pam_conv conv = { pamChat, &info };
  560. struct group *gp;
  561. int res, i;
  562. assert(wp);
  563. assert(wp->username && wp->username);
  564. assert(wp->password);
  565. assert(!wp->encoded);
  566. info.name = (char*) wp->username;
  567. info.password = (char*) wp->password;
  568. pamh = NULL;
  569. if ((res = pam_start("login", info.name, &conv, &pamh)) != PAM_SUCCESS) {
  570. return 0;
  571. }
  572. if ((res = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK)) != PAM_SUCCESS) {
  573. pam_end(pamh, PAM_SUCCESS);
  574. trace(5, "httpPamVerifyUser failed to verify %s", wp->username);
  575. return 0;
  576. }
  577. pam_end(pamh, PAM_SUCCESS);
  578. trace(5, "httpPamVerifyUser verified %s", wp->username);
  579. if (!wp->user) {
  580. wp->user = websLookupUser(wp->username);
  581. }
  582. if (!wp->user) {
  583. Gid groups[32];
  584. int ngroups;
  585. /*
  586. Create a temporary user with a abilities set to the groups
  587. */
  588. ngroups = sizeof(groups) / sizeof(Gid);
  589. if ((i = getgrouplist(wp->username, 99999, groups, &ngroups)) >= 0) {
  590. bufCreate(&abilities, 128, -1);
  591. for (i = 0; i < ngroups; i++) {
  592. if ((gp = getgrgid(groups[i])) != 0) {
  593. bufPutStr(&abilities, gp->gr_name);
  594. bufPutc(&abilities, ' ');
  595. }
  596. }
  597. bufAddNull(&abilities);
  598. trace(5, "Create temp user \"%s\" with abilities: %s", wp->username, abilities.servp);
  599. if ((wp->user = websAddUser(wp->username, 0, abilities.servp)) == 0) {
  600. return 0;
  601. }
  602. computeUserAbilities(wp->user);
  603. }
  604. }
  605. return 1;
  606. }
  607. /*
  608. Callback invoked by the pam_authenticate function
  609. */
  610. static int pamChat(int msgCount, const struct pam_message **msg, struct pam_response **resp, void *data)
  611. {
  612. UserInfo *info;
  613. struct pam_response *reply;
  614. int i;
  615. i = 0;
  616. reply = 0;
  617. info = (UserInfo*) data;
  618. if (resp == 0 || msg == 0 || info == 0) {
  619. return PAM_CONV_ERR;
  620. }
  621. if ((reply = calloc(msgCount, sizeof(struct pam_response))) == 0) {
  622. return PAM_CONV_ERR;
  623. }
  624. for (i = 0; i < msgCount; i++) {
  625. reply[i].resp_retcode = 0;
  626. reply[i].resp = 0;
  627. switch (msg[i]->msg_style) {
  628. case PAM_PROMPT_ECHO_ON:
  629. reply[i].resp = sclone(info->name);
  630. break;
  631. case PAM_PROMPT_ECHO_OFF:
  632. /* Retrieve the user password and pass onto pam */
  633. reply[i].resp = sclone(info->password);
  634. break;
  635. default:
  636. free(reply);
  637. return PAM_CONV_ERR;
  638. }
  639. }
  640. *resp = reply;
  641. return PAM_SUCCESS;
  642. }
  643. #endif /* ME_COMPILER_HAS_PAM */
  644. #if ME_GOAHEAD_JAVASCRIPT && FUTURE
  645. static int jsCan(int jsid, Webs *wp, int argc, char **argv)
  646. {
  647. assert(jsid >= 0);
  648. assert(wp);
  649. if (websCanString(wp, argv[0])) {
  650. // FUTURE
  651. return 0;
  652. }
  653. return 1;
  654. }
  655. #endif
  656. static bool parseBasicDetails(Webs *wp)
  657. {
  658. char *cp, *userAuth;
  659. assert(wp);
  660. /*
  661. Split userAuth into userid and password
  662. */
  663. cp = 0;
  664. if ((userAuth = websDecode64(wp->authDetails)) != 0) {
  665. if ((cp = strchr(userAuth, ':')) != NULL) {
  666. *cp++ = '\0';
  667. }
  668. }
  669. wfree(wp->username);
  670. wfree(wp->password);
  671. if (cp) {
  672. wp->username = sclone(userAuth);
  673. wp->password = sclone(cp);
  674. wp->encoded = 0;
  675. } else {
  676. wp->username = sclone("");
  677. wp->password = sclone("");
  678. }
  679. wfree(userAuth);
  680. return 1;
  681. }
  682. #if ME_GOAHEAD_DIGEST
  683. static void digestLogin(Webs *wp)
  684. {
  685. char *nonce, *opaque;
  686. assert(wp);
  687. assert(wp->route);
  688. nonce = createDigestNonce(wp);
  689. /* Opaque is unused. Set to anything */
  690. opaque = "5ccc069c403ebaf9f0171e9517f40e41";
  691. wfree(wp->authResponse);
  692. wp->authResponse = sfmt(
  693. "Digest realm=\"%s\", domain=\"%s\", qop=\"%s\", nonce=\"%s\", opaque=\"%s\", algorithm=\"%s\", stale=\"%s\"",
  694. ME_GOAHEAD_REALM, websGetServerUrl(), "auth", nonce, opaque, "MD5", "FALSE");
  695. wfree(nonce);
  696. }
  697. static bool parseDigestDetails(Webs *wp)
  698. {
  699. WebsTime when;
  700. char *decoded, *value, *tok, *key, *keyBuf, *dp, *sp, *secret, *realm;
  701. int seenComma;
  702. assert(wp);
  703. key = keyBuf = sclone(wp->authDetails);
  704. while (*key) {
  705. while (*key && isspace((uchar) *key)) {
  706. key++;
  707. }
  708. tok = key;
  709. while (*tok && !isspace((uchar) *tok) && *tok != ',' && *tok != '=') {
  710. tok++;
  711. }
  712. if (*tok) {
  713. *tok++ = '\0';
  714. }
  715. while (isspace((uchar) *tok)) {
  716. tok++;
  717. }
  718. seenComma = 0;
  719. if (*tok == '\"') {
  720. value = ++tok;
  721. while (*tok != '\"' && *tok != '\0') {
  722. tok++;
  723. }
  724. } else {
  725. value = tok;
  726. while (*tok != ',' && *tok != '\0') {
  727. tok++;
  728. }
  729. seenComma++;
  730. }
  731. if (*tok) {
  732. *tok++ = '\0';
  733. }
  734. /*
  735. Handle back-quoting
  736. */
  737. if (strchr(value, '\\')) {
  738. for (dp = sp = value; *sp; sp++) {
  739. if (*sp == '\\') {
  740. sp++;
  741. }
  742. *dp++ = *sp++;
  743. }
  744. *dp = '\0';
  745. }
  746. /*
  747. user, response, oqaque, uri, realm, nonce, nc, cnonce, qop
  748. */
  749. switch (tolower((uchar) *key)) {
  750. case 'a':
  751. if (scaselesscmp(key, "algorithm") == 0) {
  752. break;
  753. } else if (scaselesscmp(key, "auth-param") == 0) {
  754. break;
  755. }
  756. break;
  757. case 'c':
  758. if (scaselesscmp(key, "cnonce") == 0) {
  759. wfree(wp->cnonce);
  760. wp->cnonce = sclone(value);
  761. }
  762. break;
  763. case 'd':
  764. if (scaselesscmp(key, "domain") == 0) {
  765. break;
  766. }
  767. break;
  768. case 'n':
  769. if (scaselesscmp(key, "nc") == 0) {
  770. wfree(wp->nc);
  771. wp->nc = sclone(value);
  772. } else if (scaselesscmp(key, "nonce") == 0) {
  773. wfree(wp->nonce);
  774. wp->nonce = sclone(value);
  775. }
  776. break;
  777. case 'o':
  778. if (scaselesscmp(key, "opaque") == 0) {
  779. wfree(wp->opaque);
  780. wp->opaque = sclone(value);
  781. }
  782. break;
  783. case 'q':
  784. if (scaselesscmp(key, "qop") == 0) {
  785. wfree(wp->qop);
  786. wp->qop = sclone(value);
  787. }
  788. break;
  789. case 'r':
  790. if (scaselesscmp(key, "realm") == 0) {
  791. wfree(wp->realm);
  792. wp->realm = sclone(value);
  793. } else if (scaselesscmp(key, "response") == 0) {
  794. /* Store the response digest in the password field. This is MD5(user:realm:password) */
  795. wfree(wp->password);
  796. wp->password = sclone(value);
  797. wp->encoded = 1;
  798. }
  799. break;
  800. case 's':
  801. if (scaselesscmp(key, "stale") == 0) {
  802. break;
  803. }
  804. case 'u':
  805. if (scaselesscmp(key, "uri") == 0) {
  806. wfree(wp->digestUri);
  807. wp->digestUri = sclone(value);
  808. } else if (scaselesscmp(key, "username") == 0 || scaselesscmp(key, "user") == 0) {
  809. wfree(wp->username);
  810. wp->username = sclone(value);
  811. }
  812. break;
  813. default:
  814. /* Just ignore keywords we don't understand */
  815. ;
  816. }
  817. key = tok;
  818. if (!seenComma) {
  819. while (*key && *key != ',') {
  820. key++;
  821. }
  822. if (*key) {
  823. key++;
  824. }
  825. }
  826. }
  827. wfree(keyBuf);
  828. if (wp->username == 0 || wp->realm == 0 || wp->nonce == 0 || wp->route == 0 || wp->password == 0) {
  829. return 0;
  830. }
  831. if (wp->qop && (wp->cnonce == 0 || wp->nc == 0)) {
  832. return 0;
  833. }
  834. if (wp->qop == 0) {
  835. wp->qop = sclone("");
  836. }
  837. /*
  838. Validate the nonce value - prevents replay attacks
  839. */
  840. when = 0; secret = 0; realm = 0;
  841. decoded = parseDigestNonce(wp->nonce, &secret, &realm, &when);
  842. if (!smatch(masterSecret, secret)) {
  843. trace(2, "Access denied: Nonce mismatch");
  844. wfree(decoded);
  845. return 0;
  846. } else if (!smatch(realm, ME_GOAHEAD_REALM)) {
  847. trace(2, "Access denied: Realm mismatch");
  848. wfree(decoded);
  849. return 0;
  850. } else if (!smatch(wp->qop, "auth")) {
  851. trace(2, "Access denied: Bad qop");
  852. wfree(decoded);
  853. return 0;
  854. } else if ((when + (5 * 60)) < time(0)) {
  855. trace(2, "Access denied: Nonce is stale");
  856. wfree(decoded);
  857. return 0;
  858. }
  859. if (!wp->user) {
  860. if ((wp->user = websLookupUser(wp->username)) == 0) {
  861. trace(2, "Access denied: user is unknown");
  862. wfree(decoded);
  863. return 0;
  864. }
  865. }
  866. wfree(decoded);
  867. wfree(wp->digest);
  868. wp->digest = calcDigest(wp, 0, wp->user->password);
  869. return 1;
  870. }
  871. /*
  872. Create a nonce value for digest authentication (RFC 2617)
  873. */
  874. static char *createDigestNonce(Webs *wp)
  875. {
  876. static int64 next = 0;
  877. char nonce[256];
  878. assert(wp);
  879. assert(wp->route);
  880. fmt(nonce, sizeof(nonce), "%s:%s:%x:%x", masterSecret, ME_GOAHEAD_REALM, time(0), next++);
  881. return websEncode64(nonce);
  882. }
  883. static char *parseDigestNonce(char *nonce, char **secret, char **realm, WebsTime *when)
  884. {
  885. char *tok, *decoded, *whenStr;
  886. assert(nonce && *nonce);
  887. assert(secret);
  888. assert(realm);
  889. assert(when);
  890. if ((decoded = websDecode64(nonce)) == 0) {
  891. return 0;
  892. }
  893. *secret = stok(decoded, ":", &tok);
  894. *realm = stok(NULL, ":", &tok);
  895. whenStr = stok(NULL, ":", &tok);
  896. *when = hextoi(whenStr);
  897. return decoded;
  898. }
  899. /*
  900. Get a Digest value using the MD5 algorithm -- See RFC 2617 to understand this code.
  901. */
  902. static char *calcDigest(Webs *wp, char *username, char *password)
  903. {
  904. char a1Buf[256], a2Buf[256], digestBuf[256];
  905. char *ha1, *ha2, *method, *result;
  906. assert(wp);
  907. assert(password);
  908. /*
  909. Compute HA1. If username == 0, then the password is already expected to be in the HA1 format
  910. (MD5(username:realm:password).
  911. */
  912. if (username == 0) {
  913. ha1 = sclone(password);
  914. } else {
  915. fmt(a1Buf, sizeof(a1Buf), "%s:%s:%s", username, wp->realm, password);
  916. ha1 = websMD5(a1Buf);
  917. }
  918. /*
  919. HA2
  920. */
  921. method = wp->method;
  922. fmt(a2Buf, sizeof(a2Buf), "%s:%s", method, wp->digestUri);
  923. ha2 = websMD5(a2Buf);
  924. /*
  925. H(HA1:nonce:HA2)
  926. */
  927. if (scmp(wp->qop, "auth") == 0) {
  928. fmt(digestBuf, sizeof(digestBuf), "%s:%s:%s:%s:%s:%s", ha1, wp->nonce, wp->nc, wp->cnonce, wp->qop, ha2);
  929. } else if (scmp(wp->qop, "auth-int") == 0) {
  930. fmt(digestBuf, sizeof(digestBuf), "%s:%s:%s:%s:%s:%s", ha1, wp->nonce, wp->nc, wp->cnonce, wp->qop, ha2);
  931. } else {
  932. fmt(digestBuf, sizeof(digestBuf), "%s:%s:%s", ha1, wp->nonce, ha2);
  933. }
  934. result = websMD5(digestBuf);
  935. wfree(ha1);
  936. wfree(ha2);
  937. return result;
  938. }
  939. #endif /* ME_GOAHEAD_DIGEST */
  940. PUBLIC int websSetRouteAuth(WebsRoute *route, char *auth)
  941. {
  942. WebsParseAuth parseAuth;
  943. WebsAskLogin askLogin;
  944. assert(route);
  945. assert(auth && *auth);
  946. askLogin = 0;
  947. parseAuth = 0;
  948. if (smatch(auth, "basic")) {
  949. askLogin = basicLogin;
  950. parseAuth = parseBasicDetails;
  951. #if ME_GOAHEAD_DIGEST
  952. } else if (smatch(auth, "digest")) {
  953. askLogin = digestLogin;
  954. parseAuth = parseDigestDetails;
  955. #endif
  956. } else {
  957. auth = 0;
  958. }
  959. route->authType = sclone(auth);
  960. route->askLogin = askLogin;
  961. route->parseAuth = parseAuth;
  962. return 0;
  963. }
  964. #endif /* ME_GOAHEAD_AUTH */
  965. /*
  966. Copyright (c) Embedthis Software. All Rights Reserved.
  967. This software is distributed under commercial and open source licenses.
  968. You may use the Embedthis GoAhead open source license or you may acquire
  969. a commercial license from Embedthis Software. You agree to be fully bound
  970. by the terms of either license. Consult the LICENSE.md distributed with
  971. this software for full details and other copyrights.
  972. */