snac.c 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /* snac - A simple, minimalistic ActivityPub instance */
  2. /* copyright (c) 2022 - 2026 grunfink et al. / MIT license */
  3. #define XS_IMPLEMENTATION
  4. #include "xs.h"
  5. #include "xs_hex.h"
  6. #include "xs_io.h"
  7. #include "xs_unicode_tbl.h"
  8. #include "xs_unicode.h"
  9. #include "xs_json.h"
  10. #include "xs_curl.h"
  11. #include "xs_openssl.h"
  12. #include "xs_socket.h"
  13. #include "xs_unix_socket.h"
  14. #include "xs_url.h"
  15. #include "xs_http.h"
  16. #include "xs_httpd.h"
  17. #include "xs_mime.h"
  18. #include "xs_regex.h"
  19. #include "xs_set.h"
  20. #include "xs_time.h"
  21. #include "xs_glob.h"
  22. #include "xs_random.h"
  23. #include "xs_match.h"
  24. #include "xs_fcgi.h"
  25. #include "xs_html.h"
  26. #include "xs_po.h"
  27. #include "xs_webmention.h"
  28. #include "xs_list_tools.h"
  29. #include "snac.h"
  30. #include <sys/time.h>
  31. #include <sys/stat.h>
  32. #include <sys/wait.h>
  33. #include <limits.h>
  34. #include <stdio.h>
  35. #include <stdlib.h>
  36. #include <unistd.h>
  37. xs_str *srv_basedir = NULL;
  38. xs_dict *srv_config = NULL;
  39. xs_str *srv_baseurl = NULL;
  40. xs_str *srv_proxy_token_seed = NULL;
  41. xs_dict *srv_langs = NULL;
  42. const char *months[12] = {0};
  43. int dbglevel = 0;
  44. int mkdirx(const char *pathname)
  45. /* creates a directory with special permissions */
  46. {
  47. int ret;
  48. if ((ret = mkdir(pathname, DIR_PERM)) != -1) {
  49. /* try to the set the setgid bit, to allow system users
  50. to create files in these directories using the
  51. command-line tool. This may fail in some restricted
  52. environments, but it's of no use there anyway */
  53. chmod(pathname, DIR_PERM_ADD);
  54. }
  55. return ret;
  56. }
  57. xs_str *tid(int offset)
  58. /* returns a time-based Id */
  59. {
  60. struct timeval tv;
  61. gettimeofday(&tv, NULL);
  62. return xs_fmt("%010ld.%06ld", (long)tv.tv_sec + (long)offset, (long)tv.tv_usec);
  63. }
  64. double ftime(void)
  65. /* returns the UNIX time as a float */
  66. {
  67. xs *ntid = tid(0);
  68. return atof(ntid);
  69. }
  70. int validate_uid(const char *uid)
  71. /* returns if uid is a valid identifier */
  72. {
  73. if (!uid || *uid == '\0')
  74. return 0;
  75. while (*uid) {
  76. if (!(isalnum(*uid) || *uid == '_'))
  77. return 0;
  78. uid++;
  79. }
  80. return 1;
  81. }
  82. void srv_log(xs_str *str)
  83. /* logs a debug message */
  84. {
  85. if (xs_str_in(str, srv_basedir) != -1) {
  86. /* replace basedir with ~ */
  87. str = xs_replace_i(str, srv_basedir, "~");
  88. }
  89. xs *tm = xs_str_localtime(0, "%H:%M:%S");
  90. fprintf(stderr, "%s %s\n", tm, str);
  91. /* if the ~/log/ folder exists, also write to a file there */
  92. xs *dt = xs_str_localtime(0, "%Y-%m-%d");
  93. xs *lf = xs_fmt("%s/log/%s.log", srv_basedir, dt);
  94. FILE *f;
  95. if ((f = fopen(lf, "a")) != NULL) {
  96. fprintf(f, "%s %s\n", tm, str);
  97. fclose(f);
  98. }
  99. xs_free(str);
  100. }
  101. void snac_log(snac *snac, xs_str *str)
  102. /* prints a user debugging information */
  103. {
  104. xs *o_str = str;
  105. xs_str *msg = xs_fmt("[%s] %s", snac->uid, o_str);
  106. if (xs_str_in(msg, snac->basedir) != -1) {
  107. /* replace long basedir references with ~ */
  108. msg = xs_replace_i(msg, snac->basedir, "~");
  109. }
  110. srv_log(msg);
  111. }
  112. xs_str *hash_password(const char *uid, const char *passwd, const char *nonce)
  113. /* hashes a password */
  114. {
  115. xs *d_nonce = NULL;
  116. xs *combi;
  117. xs *hash;
  118. if (nonce == NULL) {
  119. unsigned int r;
  120. xs_rnd_buf(&r, sizeof(r));
  121. d_nonce = xs_fmt("%08x", r);
  122. nonce = d_nonce;
  123. }
  124. combi = xs_fmt("%s:%s:%s", nonce, uid, passwd);
  125. hash = xs_sha1_hex(combi, strlen(combi));
  126. return xs_fmt("%s:%s", nonce, hash);
  127. }
  128. int check_password(const char *uid, const char *passwd, const char *hash)
  129. /* checks a password */
  130. {
  131. int ret = 0;
  132. xs *spl = xs_split_n(hash, ":", 1);
  133. if (xs_list_len(spl) == 2) {
  134. xs *n_hash = hash_password(uid, passwd, xs_list_get(spl, 0));
  135. ret = (strcmp(hash, n_hash) == 0);
  136. }
  137. return ret;
  138. }
  139. char* findprog(const char *prog)
  140. /* find a prog in PATH and return the first match */
  141. {
  142. char *path_env, *path, *dir, *filename;
  143. int len;
  144. size_t filename_sz;
  145. struct stat sbuf;
  146. path_env = getenv("PATH");
  147. if (!prog || !path_env)
  148. return NULL;
  149. path_env = strdup(path_env);
  150. if (!path_env)
  151. return NULL;
  152. path = path_env;
  153. filename_sz = strlen(path) + strlen(prog) + 2;
  154. filename = xs_realloc(NULL, filename_sz);
  155. while ((dir = strsep(&path, ":")) != NULL) {
  156. /* empty entries as ./ instead of / */
  157. if (*dir == '\0')
  158. dir = ".";
  159. /* strip trailing / */
  160. len = strlen(dir);
  161. while (len > 0 && dir[len-1] == '/')
  162. dir[--len] = '\0';
  163. len = snprintf(filename, filename_sz, "%s/%s", dir, prog);
  164. if (len > 0 && len < (int) filename_sz &&
  165. (stat(filename, &sbuf) == 0) && S_ISREG(sbuf.st_mode) &&
  166. access(filename, X_OK) == 0) {
  167. free(path_env);
  168. return filename;
  169. }
  170. }
  171. free(path_env);
  172. xs_free(filename);
  173. return NULL;
  174. }
  175. int strip_media(const char *fn)
  176. /* strips EXIF data from a file */
  177. {
  178. int ret = 0;
  179. const xs_val *v = xs_dict_get(srv_config, "strip_exif");
  180. if (xs_type(v) == XSTYPE_TRUE) {
  181. /* Heuristic: find 'user/' in the path to make it relative */
  182. /* This works for ~/user/..., /var/snac/user/..., etc. */
  183. const char *r_fn = strstr(fn, "user/");
  184. if (r_fn == NULL) {
  185. /* Fallback: try to strip ~/ if present */
  186. if (strncmp(fn, "~/", 2) == 0)
  187. r_fn = fn + 2;
  188. else
  189. r_fn = fn;
  190. }
  191. xs *l_fn = xs_tolower_i(xs_dup(r_fn));
  192. /* check image extensions */
  193. if (xs_endswith(l_fn, ".jpg") || xs_endswith(l_fn, ".jpeg") ||
  194. xs_endswith(l_fn, ".png") || xs_endswith(l_fn, ".webp") ||
  195. xs_endswith(l_fn, ".heic") || xs_endswith(l_fn, ".heif") ||
  196. xs_endswith(l_fn, ".avif") || xs_endswith(l_fn, ".tiff") ||
  197. xs_endswith(l_fn, ".gif") || xs_endswith(l_fn, ".bmp")) {
  198. const char *mp = xs_dict_get(srv_config, "mogrify_path");
  199. pid_t pid = fork();
  200. if (pid == -1) {
  201. srv_log(xs_fmt("strip_media: cannot fork()"));
  202. return -1;
  203. } else if (pid == 0) {
  204. chdir(srv_basedir);
  205. execl(mp, "-auto-orient", "-strip", r_fn, (char*) NULL);
  206. _exit(1);
  207. }
  208. if (waitpid(pid, &ret, 0) == -1) {
  209. srv_log(xs_fmt("strip_media: cannot waitpid()"));
  210. return -1;
  211. }
  212. if (ret != 0)
  213. srv_log(xs_fmt("strip_media: error stripping %s %d", r_fn, ret));
  214. else
  215. srv_debug(1, xs_fmt("strip_media: stripped %s", r_fn));
  216. }
  217. else
  218. /* check video extensions */
  219. if (xs_endswith(l_fn, ".mp4") || xs_endswith(l_fn, ".m4v") ||
  220. xs_endswith(l_fn, ".mov") || xs_endswith(l_fn, ".webm") ||
  221. xs_endswith(l_fn, ".mkv") || xs_endswith(l_fn, ".avi")) {
  222. const char *fp = xs_dict_get(srv_config, "ffmpeg_path");
  223. /* ffmpeg cannot modify in-place, so we need a temp file */
  224. /* we must preserve valid extension for ffmpeg to guess the format */
  225. const char *ext = strrchr(r_fn, '.');
  226. if (ext == NULL) ext = "";
  227. xs *tmp_fn = xs_fmt("%s.tmp%s", r_fn, ext);
  228. pid_t pid = fork();
  229. if (pid == -1) {
  230. srv_log(xs_fmt("strip_media: cannot fork()"));
  231. return -1;
  232. } else if (pid == 0) {
  233. chdir(srv_basedir);
  234. /* -map_metadata -1 strips all global metadata */
  235. /* -c copy copies input streams without re-encoding */
  236. execl(fp, "-y", "-i", r_fn, "-map_metadata", "-1", "-c", "copy", tmp_fn, (char*) NULL);
  237. _exit(1);
  238. }
  239. if (waitpid(pid, &ret, 0) == -1) {
  240. srv_log(xs_fmt("strip_media: cannot waitpid()"));
  241. return -1;
  242. }
  243. if (ret != 0) {
  244. srv_log(xs_fmt("strip_media: error stripping %s %d", r_fn, ret));
  245. /* try to cleanup, just in case */
  246. /* unlink needs full path too if we are not in basedir */
  247. xs *full_tmp_fn = xs_fmt("%s/%s", srv_basedir, tmp_fn);
  248. unlink(full_tmp_fn);
  249. }
  250. else {
  251. /* rename tmp file to original */
  252. /* use full path for source because it was created relative to basedir */
  253. xs *full_tmp_fn = xs_fmt("%s/%s", srv_basedir, tmp_fn);
  254. if (rename(full_tmp_fn, fn) == 0)
  255. srv_debug(1, xs_fmt("strip_media: stripped %s", fn));
  256. else
  257. srv_log(xs_fmt("strip_media: error renaming %s to %s", full_tmp_fn, fn));
  258. }
  259. }
  260. }
  261. return ret;
  262. }
  263. int check_strip_tool(void)
  264. /* check if strip_exif tools do exist and fix their absolute path */
  265. {
  266. const xs_val *v = xs_dict_get(srv_config, "strip_exif");
  267. /* skip if unless strip_exif; return non-error */
  268. if (xs_type(v) != XSTYPE_TRUE)
  269. return 1;
  270. int ret = 1;
  271. const char *progs[] = { "ffmpeg", "mogrify" };
  272. for (int i = 0; i < (int)(sizeof(progs) / sizeof(progs[0])); i++) {
  273. xs_str *key = xs_fmt("%s_path", progs[i]);
  274. const char *val = xs_dict_get(srv_config, key);
  275. if (val == NULL) {
  276. val = findprog(progs[i]);
  277. if (val != NULL)
  278. srv_debug(1, xs_fmt("check_strip_tool: found %s in PATH at %s", progs[i], val));
  279. }
  280. if (val == NULL) {
  281. srv_log(xs_fmt("check_strip_tool: %s not found in PATH", progs[i]));
  282. ret = 0;
  283. } else if (access(val, X_OK) != 0) {
  284. srv_log(xs_fmt("check_strip_tool: %s '%s' is not executable", progs[i], val));
  285. ret = 0;
  286. } else {
  287. srv_config = xs_dict_set(srv_config, key, val);
  288. }
  289. xs_free(key);
  290. }
  291. return ret;
  292. }